之前谈到的所有算法都是内存回收的理论基础,而现在我们来谈谈关于内存回收的具体实现。
垃圾收集器分为七种:Serial 收集器,ParNew 收集器,Parallel Scavenge 收集器,Serial Old 收集器,Parallel Old 收集器,CMS 收集器,G1 收集器。
下图展现了这七种作用于不同分代的垃圾收集器,存在连线代表可以搭配使用。
垃圾收集器接下来我们依次了解一下各垃圾收集器。
(1)Serial收集器
最基本的,发展历史最悠久的单线程垃圾收集器,曾是(JDK1.3之前)虚拟机新生代收集器唯一选择。因简单高效(相对于其它的单线程收集器),没有多余的线程开销,在Client模式下是一个非常好的选择。
缺点:在它工作时必须暂停其它所有的线程(服务暂停),直到Serial收集器将所有垃圾收集完成,因此可能会产生较长的等待时间。
新生代、老年代使用串行回收;新生代复制算法、老年代标记-整理算法。
参数控制: -XX:+UseSerialGC 指定使用Serial垃圾收集器
Serial收集器运行示意图
Serial收集器.png(2)ParNew收集器
Serial收集器的多线程版本,其余行为都与Serial收集器完全一致,两者其实共用了大量相同的代码。除了Serial收集器外,唯一一个能与CMS收集器配合工作的收集器,在Server模式下的虚拟机中首选的新生代收集器。ParNew收集器在单CPU的环境中绝对不会比Serial收集器有更好的效果。
ParNew收集器运行示意图大致一样
ParNew收集器.png参数控制:
-XX:+UseParNewGC 强制指定ParNew收集器
-XX:ParallelGCThreads 限制线程数量
(3)Parallel Scaveng收集器
新生代收集器,也是复制算法的收集器,且还是并行的多线程收集器。其他的垃圾回收器都是关注如何缩短在垃圾回收时用户线程的等待时间,而Parallel Scavenge收集器则是关注系统吞吐量:吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集时间)。吞吐量越高越能代表高效率的利用CPU,因此也被称为“吞吐量优先”收集器。
Parallel Scavenge收集器与ParNew收集器的主要区别是前者拥有GC自适应的调节策略(GC Ergonomics),由参数-XX:+UserAdaptiveSizePolicy控制。
参数控制:
-XX:+UseParallelGC:使用Parallel收集器+ 老年代串行
-XX:MaxGCPauseMillis:最大垃圾回收停顿时间,一个大于0的毫秒数。该值不是越小越好,GC停顿时间是以牺牲吞吐量与新生代空间换取的。
-XX:GCTimeRatio:直接设置吞吐量大小,大于0小于100的整数,垃圾回收时间占总时间的比率,相当于吞吐量的倒数,比如设置为9,那么就是1/(1+9),最大GC时间就占总时间的10%。
-XX:+UserAdaptiveSizePolicy:开关参数,开启后就会自动根据系统当前的性能信息,动态调整新生代大小,Eden和Survivor比例,晋升老年代对象大小等参数。
Parallel Scaveng收集器运行示意图
Parallel Scaveng收集器.png(4)Serial Old收集器
作为Serial收集器的老年代版本,同样是一个单线程收集器,主要给Client模式下的虚拟机使用,在Server模式下有两大用途:①在JDK1.5及之前的版本中与Parallel Scavenge收集器搭配使用;②作为CMS收集器的备选方案,在并发收集发生 Concurrent Mode Failure 时使用。
Serial Old收集器运行示意图
Serial Old收集器.png(5)Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供。在注重吞吐量与CPU资源敏感的场合,都可以优先考虑 Parallel Scavenge收集器加Parallel Old收集器的组合
参数控制: -XX:+UseParallelOldGC 使用Parallel收集器+ 老年代并行
Parallel Old收集器运行示意图
Parallel Old 收集器.png(6)CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以牺牲吞吐量为代价获取最短回收停顿时间为目标的收集器。在重视服务的响应速度,希望系统停顿时间最短的情况下使用该收集器是最佳的选择。CMS是用于对年老代的回收,利用和应用程序线程并发的垃圾回收线程来标记清除年老代。
CMS收集器是基于“标记-清除”算法实现的,CMS收集器的GC周期由6个阶段组成。其中4个阶段与实际的应用程序是并发执行的,而其他2个阶段需要暂停应用程序线程。:
①初始标记(CMS initial mark):执行时必须暂停其他一切线程(官方称为:Stop The World),仅仅只是标记一下GC Roots能直接关联到的对象,速度很快。
②并发标记(CMS concurrent mark):这个动作发生在进行GC Roots Tracing的过程中,紧随初始标记阶段,耗时较长,但由于应用程序的线程和并发标记的线程并发执行,所以用户不会感觉到停顿。
③并发预清理(CMS concurrent precleaning):在并发标记阶段可能有新进入老年代的对象,并发预清理就是查找这些新对象以减少下一步的负担。
④重新标记(CMS remark):执行时也必须暂停其他一切线程。为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
⑤并发清除(CMS concurrent sweep):清理垃圾对象,这个阶段收集器线程和应用程序线程并发执行,耗时较长。
⑥并发重置(CMS concurrent reset):重置CMS收集器的数据结构,等待下一次的垃圾回收执行。
CMS收集器运行示意图
CMS收集器优点:并发收集,低停顿
缺点:
①对CPU资源非常敏感,在并发阶段可能会导致系统吞吐量下降。
②在CMS并发清理阶段用户线程也在运行,用户线程就会产生新的垃圾,这部分垃圾在被标记后会被延迟到下一次GC时才被清理掉,这一部分垃圾就叫做“浮动垃圾”。在CMS收集器会预留一部分空间给用户线程使用,当预留空间不足时就会导致“Concurrent Mode Failure”失败。所以当CMS收集器无法处理浮动垃圾时,空间不足可能导致“Concurrent Mode Failure”失败而导致另一次的 Full GC的产生。
③CMS收集器是基于“标记-清除”算法实现的,所以收集结束时可能会产生大量的空间碎片,可以通过参数设置碎片整理。
参数
-XX:+UseConcMarkSweepGC:使用CMS收集器
– XX:CMSInitiatingOccupancyFraction =n :CMS收集器的启动阀值,该值代表老年代空间已使用的比例。该值太高可能会导致无法预留足够的空间给用户线程,从而导致“Concurrent Mode Failure”失败。
-XX:+ UseCMSCompactAtFullCollection:Full GC后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长
-XX:+CMSFullGCsBeforeCompaction 设置进行几次Full GC后,进行一次碎片整理
-XX:ParallelCMSThreads 设定CMS的线程数量(一般情况约等于可用CPU数量)
(7)G1收集器
面向服务端应用的,基于“标记-整理”算法实现的垃圾收集器,在JDK1.7u4版本及以后才作为正式的商用,在未来准备替换掉JDK1.5中发布的CMS收集器。
与其他收集器相比,G1具备以下优势:
①并行与并发:通过多核与多CPU优势,缩短系统停顿时间,且不影响Java程序的执行。
②分代收集:G1不需要与其他收集器配合就能独立管理整个GC堆。
③空间整合:不会产生空间碎片。
④可预测的停顿:区别于CMS,G1最大的优势就是可以系统停顿时间可控可预测。
对垃圾收集器的总结
不再将老年代与新生代进行物理隔离,而是将堆规划为大小相等的独立区域(Region)划分内存空间。根据垃圾的堆积的价值大小,G1会在后台维护一个优先列表,优先回收垃圾价值最大的区域。
G1收集器的步骤分为以下四个步骤:
①初始标记(Initial Mark):执行时必须暂停其他一切线程(官方称为:Stop The World),标记一下GC Roots能直接关联到的对象,并且修改TAMS值,让下一阶段用户程序并发执行时能够在正确的区域进行创建对象,执行速度很快。
②并发标记(Concurrent Marking):从GC Roots开始对堆中对象进行可达性分析,耗时间长,但可以与用户线程并发执行。
③最终标记(Final Marking):执行时也必须暂停其他一切线程。为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。
④筛选回收(Live Data Counting and Evacuation):对各个区域(Region)的回收价值与成本排序,然后根据用户指定的GC停顿时间来制定回收计划。
参数
-XX:+UseG1GC :启动GC收集器
-XX:G1HeapRegionSize : 设定堆中区域(Region)的大小,大小区间只能是2的幂次方
对垃圾收集器的总结
垃圾收集器综合对比.png
网友评论