前言
在《Java虚拟机规范》中并没有规定垃圾应该如何回收,只是提出了这样一种理论,因此不同的厂商也有不同的实现方式,在众多的JVM中也存在着形形色色的GC,在此主要说的也是HotSpot VM中出现过的垃圾收集器。在JDK 7之前也就是G1出现之前,垃圾收集器是分代收集的按照新生代和老年代有不同的GC来进行内存回收,在G1之后出现的GC基本都是面向全堆栈回收,因此也可以对GC做不同的分类。
新生代GC
Serial GC
这是最基础也是历史最悠久的收集器,在JDK 1.3.1之前,HotSpot中新生代唯一的收集器选择,在进行收集的时候会暂停所有工作线程直到收集结束,由于其单线程模式,也导致了STW时间过长,用户应用处于长时间假死状态,非常影响用户体验。但也是所有收集器中内存资源消耗最少的,在单核或者核心较小的服务器环境,也能体现出其特有的优势。在客户端模式下是一个不错的选择。
Parallel Scavenge GC
基于标记复制算法实现的收集器,其目标在于达到一个可控制的吞吐量(处理器用于运行用户代码时间与处理器总消耗时间的比值),良好的响应速度可以提高用户体验,高吞吐量可以最高效率的利用处理器资源,尽快完成运算任务,适合运算多而交互少的分析任务。
ParNew GC
实质上是Serial的多线程版本,线程的数量一般为CPU的核数,除了在收集垃圾采用多线程并行收集外,其他地方与Serial并无较多差异,是不少在服务端模式下运行的虚拟机的首选新生代收集器。ParNew与Parallel Scavenge的区别在于,ParNew在Parallel Scavenge的基础上做了一个增强,使得它可以CMS配合使用,前者侧重于低延迟,后者侧重于高吞吐。
老年代GC
Serial Old GC
相对于Serial收集器,Serial Old专用于处理老年代GC,使用标记整理算法,主要也是在客户端模式下使用,如果在服务端模式下,在JDK 1.5之前与Parallel Scavenge配合使用,或者作为CMS失败后的预案,在Concurrent Mode Failure时使用。
Parallel Old GC
PArallel Old是Parallel Scavenge的老年代版本,基于标记整理算法,支持多线程并发收集,在JDK 6提供,在Parallel Old出现之前,Parallel Scvenge只能和Serial Old搭配使用,比较尴尬,CMS无法与之配合,而Serial Old又不适合服务端模式,因此在此之前使用较多的也是ParNew + CMS组合。在吞吐量优先的场景中,PS + PO的组合实至名归。
CMS GC
JDK 5推出的一个划时代意义的收集器,首款真正做到了并发收集的垃圾收集器,实现了垃圾收集线程与用户线程同时运行。无法与Parallel Scavenge配合工作,因此CMS的出现也巩固了ParNew的地位。CMS使用了标记清除算法,一般分为四个阶段,初始标记->并发标记->重新标记->并发清除,由于初始标记和并发标记的存在,大大减小了STW的时间,但是CMS无法很好的处理浮动垃圾(并发清理阶段,用户线程产生的垃圾,无法被及时清除,保留在下一个GC阶段),为了处理浮动垃圾,CMS需要老年代预留一部分内存,通过设定的阈值来确定GC的时机。CMS也是在一次次不完美的过程中尝试完美。
不分代GC
G1 GC
G1在JDK 7中进入Experimental状态,在JDK 8中日趋成熟,在JDK 9中开始正式成为HotSpot的默认GC,替代了服务端主打的PS + PO组合收集器,此后也在Oracle官方被称为全功能垃圾收集器(Full-Featured Garbage Collector),主要面向服务端。G1是垃圾收集器发展史上具有里程碑式的成果,开创了面向局部收集的设计,每一个Region都可以扮演新生代的Eden、Survivor或者老年代空间。
低延迟垃圾收集器(衡量指标:内存占用、吞吐量、延迟)
Shenandoah GC
第一款不是Sun或者Oracle设计的GC,甚至在OracleJDK 12中被明确拒绝的GC,因此只能存在与OpenJDK中,开源比收费的功能反而更多。相比于G1更像是G1的继承者,多线程Full GC的支持、类似于Region的设计有着优于G1的更多设计,都是为了降低延迟。
Z GC
JDK 11开始加入的GC,与Shenandoah目标类似,但设计思路又大有不同,Shenandoah更类似于G1的延伸,ZGC更像Azul公司PGC和C4的复刻。ZGC使用染色指针技术来作为其标志性的并发整理算法实现,支持更大的内存管理和更高效的垃圾收集。
Epsilon GC
JDK 11中依然出现了一个实验性质的GC,不能够进行垃圾收集的垃圾收集器,相比于G1、Shenandoah、ZGC复杂的垃圾收集算法和设计实现,Epsilon有点反其道而行之的意味,事实上一个GC的功能不仅仅是回收这个动作,还要负责堆管理布局,对象分配以及与解释器的协同工作等,如果在某些不需要回收仍然可以正常运行的Java虚拟机系统中Epsilon似乎是一种更好的选择。解决问题的方式固然重要,处理问题的思路也很重要。
JDK8中GC组合
之所以存在这么多的垃圾收集器,也就说明了没有一个完美的GC来适应所有场景,因此面对不同的场景,分代收集和全堆栈收集也给JVM调优指明了思路,了解不同GC的设计特点,才能更好的选择。
常见GC搭配组合:
JDK 8之前,GC存在明确的分代模型,G1开始引入了Region的布局概念,每一个Region都可以按照需要扮演新生代的Eden或者Survivor空间以及老年代空间。由于在JDK 8中G1尚不成熟,因此没有成为默认收集器,JDK 8中默认使用的PS + PO的组合模式,因此在JDK8中也可以根据实际情况,对垃圾收集器进行适当调整。JDK 9中已经将G1设置为默认的垃圾收集器,从这个版本之后,JVM调优会越来越弱化,开发人员以更多的精力来关注上层应用开发。
使用命令查看当前版本JVM都默认GC
java -XX:+PrintCommandLineFlags -version
输出结果:
-XX:InitialHeapSize=257033280 -XX:MaxHeapSize=4112532480 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
java version "1.8.0_301"
Java(TM) SE Runtime Environment (build 1.8.0_301-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.301-b09, mixed mode)
可以看到初始化堆内存和最大堆内存大小,默认开启指针压缩,以及默认GC信息。