如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的实践者
这7种作用于不同分代的垃圾收集器,如果两个收集器之间有连线,就说明它们可以搭配使用,收集器所处的位置表示它们是属于新生代收集器或老年代收集器。
Serial收集器
Serial收集器是一个单线程工作的收集器,但它的“单线程”的意义不仅仅是它只会使用一个处理器或一个收集线程去完成垃圾收集工作,更重要的是强调它在进行垃圾回收的时候,必须暂停其他所有工作线程,直到它回收结束为止。
“Stop The World”这项工作是由虚拟机在后台自动发起完成的,在用户不可知、不可控的情况下,把用户正常工作的线程全部停掉,这对很多应用来说是不能接受的。
从JDK1.3到现在最新的JDK17,HotSpot虚拟机开发团队为消除或者降低用户线程因垃圾收集而导致停顿的努力一直在持续进行着,从Serial收集器到Parallel收集器,到Concurrent Mark Sweep(CMS)和Garbage First(G1)收集器,到现在收集器最前沿成功Shenandoah和ZGC等,用户线程的停顿时间在持续缩短,但仍然没办法彻底消除。
Serial依然是Hotspot虚拟机运行在客户端模式下的默认新生代收集器,它优于其他收集器的地方是简单而高效(与其他收集器的单线程相比),是所有收集器额外内存消耗最小的,由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。
ParNew收集器
ParNew收集器是Serial收集器的多线程并行版本,除了使用多条线程进行垃圾收集之外,其余的行为包括Serial收集器可用的所有控制参数。
例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一致。
ParNew是运行在服务器端模式下的HotSpot虚拟机,很重要的原因是:除了Serial收集器之外,目前只有它能与CMS收集器配合工作,ParNew默认开启的收集线程数与处理器核心数量相同,可以通过-XX:ParallelGCThreads参数来限制垃圾收集的线程数。
CMS收集器是HotSpot虚拟机中第一款真正意义上支持并发的垃圾收集器,它首次实现了让垃圾收集线程与用户线程同时工作。ParNew收集器是激活CMS后的默认新生代收集器,使用-XX:+UseConcMarkSweepGC选项。
Parallel Scavenge收集器
Parallel Scavenge表面上看与ParNew非常相似,同样是一款新生代收集器,基于标记——复制算法实现的收集器,能够并行收集的多线程收集器。但是,Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。
吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 运行垃圾代码时间)
Serial Old 收集器
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记——整理算法。
Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记——整理算法实现。
直到Parallel Old出现后,“吞吐量优先”收集器终于有了名副其实的搭配组合,在注重吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器这个组合。
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很多Java应用集中在互联网网站或基于浏览器的B/S系统的服务端上,这类应用通常都较为关注服务的响应速度,希望系统停顿时间尽可能短,给用户带来良好的交互体验。
CMS收集器整个过程分为四个步骤:
- 初始标记(STW)
- 并发标记
- 重新标记(STW)
- 并发清除
在初始标记、重新标记这两个步骤仍然需要“Stop The World”。
初始标记仅仅标记一下GC Roots能直接关联到的对象,速度很快;
并发标记是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程毕竟耗时但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行;
重新标记是为了修正并发标记期间,因用户程序继续运行而导致标记产生变化的那部分对象的标记记录(增量更新),这个阶段耗时比初始阶段稍长,比并发阶段稍短;
并发清除阶段,清理删除掉标记阶段判断的已经死亡的对象,这个阶段也是可以和用户线程同时并发的。
Garbage First 收集器
Garbage First(G1)收集器是垃圾收集器技术发展历史上的里程碑的成果,它开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。
G1不在坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的对立区域(Region),每个区域都可以根据需要,扮演新生代的Eden空间、Survivor空间或者老年代空间。
收集器能够堆扮演不同角色的Region采用不同的策略去处理。
G1收集器运行的四个步骤:
初始标记:只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确的在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时短。
并发标记:从GC Roots开始对堆对象进行可达性分析,递归扫描整个堆的对象图,找出要回收的对象,这阶段耗时较长,可与用户程序并发执行。当对象图扫描完,还要重新处理SATB记录下的在并发时有引用变动的对象。
最终标记:对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。
筛选回收:负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把那一部分Region的存活对象复制到空的Region中,再清理掉整个旧的Region的全部空间。
各款收集器的并发情况
常见垃圾回收器组合参数设定
- -XX:+UseSerialGC = Serial + Serial Old
默认情况下不会是这种选项,HotSpot虚拟机会根据计算及配置和JDK版本自动选择收集器
- -XX:+UseParNewGC = ParNew + SerialOld
这个组合已经很少用(在某些版本中已经废弃)
-XX:+UseConcMarkSweepGC = ParNew + CMS + Serial Old
-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认)
-XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
UseParallelGC 与 UseParallelOldGC 的区别:
-XX:+UseParallelGC = Parallel Scavenge + Parallel Old
-XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
-XX:-UseParallelOldGC = Parallel Scavenge + Serial Old
-XX:+UseG1GC = G1
查看JDK8默认使用的垃圾收集器
1.8默认的垃圾收集器是:Parallel Scavenge + Parallel Old
java +XX:+PrintCommandLineFlags -version
java -XX:+PrintGCDetails -version