JVM(六:垃圾收集器)
上图展示了七种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用,图中收集器所处的区域,则表示它是属于新生代收集器抑或是老年代收集器。G1在新生代和老年代都可以用。
Serial收集器
Serial/Serial Old收集器是一个单线程工作的收集器。
优点:简单而高效。内存消耗少。
缺点:进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。
ParNew收集器
ParNew/Parallel Old收集器实质上是Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其余的行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio【Eden和Survivor比例】 、-XX:PretenureSizeThreshold【大对象进老年代】、-XX:HandlePromotionFailure【担保】等)、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一致。
可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数
ParNew收集器除了支持多线程并行收集之外,其他与Serial收集器相比并没有太多创新之处,但它却是不少运行在服务端模式下的HotSpot虚拟机,尤其是JDK 7之前的遗留系统中首选的新生代收集器,其中有一个与功能、性能无关但其实很重要的原因是:除了Serial收集器外,目前只有它能与CMS收集器配合工作。
Parallel Scavenge收集器
Parallel Scavenge收集器也是一款新生代收集器,同样是基于标记-复制算法实现的收集器。
与其他垃圾收集器的区别是:Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值。
![]()
如果虚拟机完成某个任务,用户代码加上垃圾收集总共耗费了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
而高吞吐量则可以最高效率地利用处理器资源,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的分析任务。(无需用户体验的服务)
缺点是停顿时间和Serial,ParNew一样,比较长,需要暂停所有用户线程
Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是
1,控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数
收集器将尽力保证内存回收花费的时间不超过用户设定值。设定太小的话会导致垃圾回收频繁。
2,直接设置吞吐量大小的-XX:GCTimeRatio参数。
垃圾收集时间占总时间的比率,相当于吞吐量的倒数。
3,开关参数-XX:+UseAdaptiveSizePolicy。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。这种调节方式称为垃圾收集的自适应的调节策略(GC Ergonomics)。
由于与吞吐量关系密切,Parallel Scavenge收集器也经常被称作“吞吐量优先收集器”。自适应调节策略也是Parallel Scavenge收集器区别于ParNew收集器的一个重要特性。
CMS收集器
CMS(Concurrent Low Pause Collector)收集器是HotSpot虚拟机中第一款真正意义上支持并发的垃圾收集器,它首次实现了让垃圾收集线程与用户线程(基本上)同时工作。
CMS收集器是一种以获取最短回收停顿时间为目标的收集器。适合关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验的应用。
CMS收集器是基于标记-清除算法实现的。
CMS运作过程分为四个步骤:
1,初始标记(CMS initial mark):需要Stop The World;(标记一下GCRoots能直接关联到的对象,速度很快)
2,并发标记(CMS concurrent mark);(从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长)
3,重新标记(CMS remark):需要Stop The World;(增量更新:修正并发标记期间,因用户程序继续运作而导致标记产生变动那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些)
4,并发清除(CMS concurrent sweep);(清理删除掉标记阶段判断的已经死亡的对象)
优点:并发收集、低停顿;
缺点:
1,对处理器资源非常敏感;并发阶段会因为占用了一部分线程,而导致应用程序变慢,降低总吞吐量。
CMS默认启动的回收线程数是(处理器核心数量+3)/4,也就是说,如果处理器核心数在四个或以上,并发回收时垃圾收集线程只占用不超过25%的处理器运算资源,并且会随着处理器核心数量的增加而下降。但是当处理器核心数量不足四个时,CMS对用户程序的影响就可能变得很大。
2,无法处理“浮动垃圾”;在CMS的并发标记和并发清理阶段,用户线程是还在继续运行的,程序在运行自然就还会伴随有新的垃圾对象不断产生。CMS无法在当次收集中处理掉它们,只好留待下一次垃圾收集时再清理掉。这一部分垃圾就称为“浮动垃圾”。
由于在垃圾收集阶段用户线程还需要持续运行,那就还需要预留足够内存空间提供给用户线程使用,因此CMS收集器不能像其他收集器那样等待到老年代几乎完全被填满了再进行收集,必须预留一部分空间供并发收集时的程序运作使用。
在JDK5的默认设置下,CMS收集器当老年代使用了68%的空间后就会被激活,这是一个偏保守的设置,如果在实际应用中老年代增长并不是太快,可以适当调高参数-XX:CMSInitiatingOccu-pancyFraction的值来提高CMS的触发百分比,降低内存回收频率,获取更好的性能。
到了JDK 6时,CMS收集器的启动阈值就已经默认提升至92%。但这又会更容易面临另一种风险:要是CMS运行期间预留的内存无法满足程序分配新对象的需要,就会出现一次“并发失败”(Concurrent Mode Failure),这时候虚拟机将不得不启动后备预案:冻结用户线程的执行,临时启用Serial Old收集器来重新进行老年代的垃圾收集,但这样停顿时间就很长了。
即为上篇说到的“和稀泥式”解决方案:内存空间的碎片化程度已经大到影响对象分配时,再采用标记-整理算法收集一次,以获得规整的内存空间。
3,CMS收集结束时会有大量空间碎片产生。
为了解决这个问题,CMS收集器提供了一个-XX:+UseCMS-CompactAtFullCollection开关参数,用于在CMS收集器不得不进行Full GC时开启内存碎片的合并整理过程,需要Stop The World。停顿时间又会变长。
因此虚拟机设计者们还提供了另外一个参数-XX:CMSFullGCsBefore-Compaction,这个参数的作用是要求CMS收集器在执行过若干次(数量由参数值决定)不整理空间的Full GC之后,下一次进入Full GC前会先进行碎片整理(默认值为0,表
示每次进入Full GC时都进行碎片整理)。
由于在整个过程中耗时最长的并发标记和并发清除阶段中,垃圾收集器线程都可以与用户线程一起工作,所以从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
重新标记的原因
“标记”阶段是所有追踪式垃圾收集算法的共同特征,为什么需要重新标记,需要先理解下三色标记。
---白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始的阶段,所有的对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达。
---黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。黑色的对象代表已经扫描过,它是安全存活的,如果有其他对象引用指向了黑色对象,无须重新扫描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。
---灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过。
关于可达性分析的扫描过程,发挥一下想象力,把它看作对象图上一股以灰色为波峰的波纹从黑向白推进的过程,如果用户线程此时是冻结的,只有收集器线程在工作,那不会有任何问题。但如果用户线程与收集器是并发工作呢?
Wilson于1994年在理论上证明了,当且仅当以下两个条件同时满足时,会产生“对象消失”的问题,即原本应该是黑色的对象被误标为白色:(黑色对象不会重新扫描)
1,赋值器插入了一条或多条从黑色对象到白色对象的新引用;
2,赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。
因此,我们要解决并发扫描时的对象消失问题,只需破坏这两个条件的任意一个即可。由此分别产生了两种解决方案:增量更新(Incremental Update)和原始快照(Snapshot At The Beginning,SATB)。
增量更新
要破坏的是条件一,当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。这可以简化理解为,黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象了。
原始快照
破坏的是条件二,当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。这也可以简化理解为,无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来进行搜索。
摘抄:《深入理解Java虚拟机:JVM高级特性与最佳实践》-第三章