垃圾收集器与内存分配策略_hotspot垃圾收集算法实现和垃圾收集器

HotSpot算法实现

1. 枚举根节点

从可达性分析中从GC Roots节点中找引用链这个操作为例,可作为GC Roots的节点主要在全局性的引用(常量或类静态属性)与执行上下文(例如栈帧中的本地变量表)中。如果逐个检查,消耗很多资源

另外,可达性分析对执行时间的敏感还体现在GC停顿上,因为这项分析工作必须在一个能保证一致性的快照中进行-这里的一致性指的是在整个分析期间整个执行系统看起来就像是冻结在某个时间点上。如果一致性无法保证,分析结果就无法保证。

这就是GC进行时,必须停顿所有java执行线程的一个原因.即使在几乎不会发生停顿的CMS收集器中,枚举根节点也是必须停顿的

那么主流的虚拟机是如何检查完所有的GC Roots呢?

使用一组称为OopMap的数据结构,在类加载完成,将对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程中,也会在特定的位置记录栈和寄存器中哪些位置是引用。

2. 安全点

HotSpot不会为每条指令都生成OopMap,只是在特定的位置记录这些信息,这些位置称为安全点。程序执行时并非所有的地方都能停顿下来开始GC,只有到达安全点时才能暂停。

安全点的选定基本上是以“是否具有让程序长时间执行的特性”为标准进行选定的,“长时间执行”的最明显特征就是指令序列复用,例如方法复用、循环跳转、异常跳转等,这些功能指令才会产生Safepoint

3. 安全区域

安全点机制保证了程序执行时,在不太长的时间内就会遇到可进入GC的Safepoint。但是程序不执行时候?

典型的例子就是线程处于Sleep状态或者Blocked状态,这时候线程无法响应JVM的中断请求,到安全点去中断挂起,

JVM也显然不可能等待线程重新被分配CPU时间,这种情况需要安全区域解决。

安全区域指的是在一段代码时间内,引用关系不会发生变化。在这个区域中的任何地方开始GC都是安全的。

在线程执行到安全区域,首先标识自己已经进入了safe region,那样如果JVM发起GC时候,不用管标识自己为Safe Region状态的线程了。线程离开安全区域,检查系统是否完成根节点枚举,完成,线程就会执行执行,否则等待直到可以离开安全区域的信号

垃圾收集器

Serial收集器

一个单线程的垃圾收集器,单线程指的是进行垃圾收集时候,必须暂停其他所有的工作线程,直到收集结束

垃圾收集器与内存分配策略_hotspot垃圾收集算法实现和垃圾收集器_第1张图片

它是虚拟机运行在Client模式下默认的新生代收集器。有着优于其他垃圾收集器的地方:简单而高效,对于限定单个CPU的环境来说,Serial收集器没有线程交互的开销,专心做垃圾收集可以获得最高的单线程收集效率

Serial Old收集器

Serial收集器的老年代版本,同样是一个单线程的垃圾收集器,使用标记-整理算法。

一种用途在JDK1.5以前版本和Parallel SCavenge收集器搭配使用

一种作为CMS收集器的后备方案

ParNew收集器

是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集外,其余行为包括Serial收集器可用的所有控制参数、收集算法、Stop The World 、对象分配规则、回收策略等都与Serial收集器完全一样。

垃圾收集器与内存分配策略_hotspot垃圾收集算法实现和垃圾收集器_第2张图片

作为许多运行在Server模式下的虚拟机中首选的新生代垃圾收集器,其中一个与性能无关的重要原因是,除了Serial收集器外,目前只有它能喝CMS收集器配合工作。

从ParNew收集器开始,后面会接触到几款并发和并行的收集器。在垃圾收集器的语境中解释如下:

并行(Parallel): 指多条垃圾收集线程并行工作,既然垃圾收集器在工作了,用户线程必须是等待状态的

并发(Concurrent): 指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个CPU上。

Parallel Scavenge收集器

是新生代的垃圾收集器,也是使用复制算法的收集器,并且是并行的多线程收集器,看上去和ParNew都一样,有什么特殊?

Parrallel Scavenge收集器的特点是它的关注点和其他不同,CMS等垃圾收集的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,Parrralle Scavenge目标是达到一个可控制的吞吐量

Parallel Old收集器

作为Parallel SCAvenge收集器的老年代版本,使用多线程和标记-整理算法搭配新生代垃圾收集器Parallel Scavenge使用

垃圾收集器与内存分配策略_hotspot垃圾收集算法实现和垃圾收集器_第3张图片

CMS收集器

CMS收集器是一种以获取最短回收停顿时间为目标的收集器。CMS收集器是基于标记-清除算法实现的,运行过程分为:

1> 初始标记

2> 并发标记

3> 重新标记

4> 并发清除

其中,

初始标记,重新标记这两个步骤仍然需要Stop the world 。

初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快。

并发标记阶段就是进行GC Roots Tracing的过程

重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。这个阶段的停顿时间一般会比初始标记阶段稍长,但是远比并发标记时间短。

整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以和用户线程一起工作,所以,总体上看,CMS收集器的内存回收过程是与用户线程一起并发执行的。

优点

并发收集、低停顿

缺点

对CPU资源非常敏感,当CPU不足4个,性能下降很多。

无法处理浮动垃圾(由于用户线程是运行着,无法当次清理运行中产生的垃圾)

采用标记-清除算法,产生的内存空间不连续的问题,无法放置大对象,会再次触发一次垃圾收集

G1收集器

一款面向服务器的垃圾收集器,G1具备如下特点:

1> 并行和并发

2> 分代收集。不需要其他收集器配合就能管理整个GC堆,按照新生代和老年代分开处理

3> 空间整合。和CMS不同,G1从整体上看是基于"标记-整理"算法实现的收集器,从局部(两个Region之间)上看是基于复制算法实现的,也就是G1运行期间不会产生内存空间碎片的,收集后能提供规整的可用内存

4> 可预测的停顿。

G1收集器,将java堆划分为多个大小相等的独立区域,虽然还保留新生代和老年代的概念,但是新生代和老年代不再是物理隔离了。

G1收集器的运作大致可划分为以下几个步骤:

1> 初始标记

2> 并发标记

3> 最终标记

4> 筛选回收

初始标记阶段只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这阶段需要停顿线程,但是耗时很短

并发标记阶段是从GC Roots开始对堆中对象进行可达性分析,找出存活对象,耗时比较长

最终标记阶段则是为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,将这段时间对象变化记录到线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set logs的数据合并到 remembered Set中,需要停顿线程,但是可以并行执行。

筛选回收阶段对么个region的回收价值和成本进行排序,根据用户所期望的GC停顿时间制定回收计划。

你可能感兴趣的:(JVM)