JVM学习(四):垃圾回收器
前面我们学习了JVM的垃圾回收算法,如果说,垃圾回收算法是内存回收的理论,那么今天我们要讲的垃圾回收器,就是内存回收的具体实现了。在Java虚拟机规范中对垃圾回收器应该如何实现并没有任何的规定,因此不同厂商、不同版本的虚拟机所提供的垃圾回收器都可能会有很大差别。本篇博文是按照《深入理解JVM》所讲进行总结的,该书基于JDK1.7上的HotSpot虚拟机。
先上张图,看看有哪些垃圾回收器,它们是用在哪些地方的?
如果两个收集器之间存在连线,则说明它们可以搭配使用
简单进行分类划分一下:
- 新生代回收器:Serial、ParNew、Parallel Scavenge,使用复制算法
- 老年代回收器:CMS、Serial Old、Parallel Old,使用标记-整理或标记-清除算法
- 整堆回收器:G1
首先,先来了解一些概念
并行:指多条垃圾收集线程并行工作,但此时用户线程任然处于等待状态
并发:指用户线程与垃圾收集线程同时执行(但并不一定并行,可能会交替执行),用户程序在继续执行,而垃圾收集程序运行于另外一个CPU上
吞吐率=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
1.Serial
Serial收集器是最基本、历史最悠久的收集器,特点是简单高效,在JDK1.3.1之前,是新生代收集的唯一选择。它是单线程的,在进行垃圾回收工作中,必须暂停其他所有的工作线程,直到它结束(Stop The World)。
应用场景:适用于Client模式下的虚拟机
运行示意图:
2.ParNew
ParNew收集器就是Serial收集器的多线程版本,除了多线程之外,其余行为包括Serial收集器可用的所以控制参数(例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold 、-XX:HandlePromotionFailure)、收集算法、Stop The World、对象分配规则、回收策略等和Serial完全一样。
应用场景:适用于Server模式下的虚拟机,除此之外,它是除了Serial收集器外,唯一一个能与CMS(JDK1.5推出的一款具有划时代意义的收集器,后面会讲)收集器配合工作的。
运行示意图:
3.Parallel Scavenge收集器
Parallel Scavenge收集器是并行多线程的,其特点是其关注点与其他收集器不同。例如:CMS等收集器关注点是尽可能地缩短垃圾回收时用户线程的停顿时间,而Parallel Scavenge收集器的目的是达到一个可控制的吞吐量。它提供了两个参数用于精准控制吞吐量,分别是-XX:MaxGCPauseMillis和-XX:GCTimeRatio.
GC自适应调节策略:可设置-XX:+UseAdptiveSizePolicy参数。当开关打开时不需要手动指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(XX:PretenureSizeThreshold)等,虚拟机会根据系统的运行状况收集性能监控信息,动态设置这些参数以提供最优的停顿时间和最高的吞吐量,这种调节方式称为GC的自适应调节策略。
4.Serial Old
Serial Old收集器是Serial收集器的老年代版本,同样是一个单线程收集器,使用“标记–整理”算法。
应用场景:主要也是使用在Client模式下的虚拟机中。也可在Server模式下使用。
Server模式下主要的两大用途:
- 在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用。
- 作为CMS收集器的后备方案,在并发收集Concurent Mode Failure时使用。
5.Parallel Old
Parallel Scavenge收集器的老年代版本,使用多线程和“标记–整理”算法,该收集器在JDK1.6中才开始提供。在早期版本中,新生代的Parallel Scavenge收集器一直处于比较尴尬的局面,因为若是新生代选择了Parallel Scavenge收集器,那么老年代只能选择Serial Old收集器(原因:Parallel Scavenge无法与CMS配合使用)。而又因为Serial Old收集器在服务端应用性能的拖累,使得Parallel Scavenge未必能在整体应用上获得最大吞吐量。
应用场景:注重高吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge+Parallel Old 收集器。
运行示意图:
6.CMS
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收暂停时间为目的的收集器。特点是基于“标记–清除”算法实现,并发收集、低停顿。
CMS的运作过程总体分为四步:(整个CMS回收过程是和用户线程一起进行的)
- 初始标记:标记GC Roots能直接到的对象。速度很快但是仍存在Stop The World问题。
- 并发标记:进行GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行。
- 重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。仍然存在Stop The World问题。
- 并发清除:对标记的对象进行清除回收。
应用场景:适用于注重服务的响应速度,希望系统停顿时间最短,给用户带来更好的体验等场景下。如web程序、b/s服务。
运行示意图:
新生代GC(Minor GC):指发生在新生代的垃圾回收动作,因为Java对象在新生代大部分都是朝生夕灭,所以Minor GC会非常频繁,一般回收速度也很快
老年代GC(Major GC/Full GC):指发生在老年代的垃圾回收动作,出现了Major GC一般至少会伴随一次Minor GC(但也非绝对),Major GC的速度比Minor GC一般慢10倍
CMS的三大缺点:
- 对CPU资源很敏感
- 无法处理浮动垃圾,可能出现Concurrent Model Failure失败而导致另一次Full GC的产生。
- 因为采用标记-清除算法所以会存在空间碎片的问题,导致大对象无法分配空间,不得不提前触发一次Full GC。
7.G1
G1(Garbage-First)收集器是当今收集器技术发展的最前沿成果之一。它是一款面向服务端应用的垃圾收集器,与其他垃圾收集器收集器相比,具有以下特点:
- 并发和并行
- 分代收集
- 空间整合
- 可预测的停顿
G1收集器回收步骤:
- 初始标记:仅标记GC Roots能直接到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象。(需要线程停顿,但耗时很短。)
- 并发标记:从GC Roots开始对堆中对象进行可达性分析,找出存活对象。(耗时较长,但可与用户程序并发执行)
- 最终标记:为了修正在并发标记期间因用户程序执行而导致标记产生变化的那一部分标记记录。且对象的变化记录在线程Remembered Set Logs里面,把Remembered Set Logs里面的数据合并到Remembered Set中。(需要线程停顿,但可并行执行。)
- 筛选回收:对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。(可并发执行)
运行示意图:
还有关于G1的详情,可以参考“博文推荐”的第一篇博客