前言:
如果说垃圾收集算法是内存回收的方法论,那么收集器就是内存回收的实践者,上一篇我们分享了 JVM 中的垃圾回收算法,本篇我来分享一下垃圾回收算法在垃圾回收器中的具体使用。
JVM 系列文章传送门
场景的垃圾回收器
垃圾收集器在 Java 虚拟机规范中没有进行过多的规定,可以由不同的厂商、不同版本的 JVM 来实现,由于 JDK 的版本处于高速迭代过程中,因此 Java 发展至今已经衍生了众多的垃圾回收器,从不同角度分析垃圾收集器,可以将垃圾收集器分为不同的类型,例如按线程数可以分为单线程(串行)垃圾回收器和多线程(并行)垃圾回收器,按照工作模式分,可以分为独占式和并发式垃圾回收器,按工作的内存区间分,又可分为年轻代垃圾回收器和老年代垃圾回收器, 实际使用时,可以根据实际的使用场景选择不同的垃圾回收器,常见的垃圾收集器如下:
- Serial 垃圾收集器。
- ParNew 来及收集器。
- Parallel Scavenge 垃圾收集器。
- CMS 垃圾收集器。
- G1 垃圾收集器。
- ZGC 垃圾收集器。
Serial 垃圾收集器
Serial 垃圾收集器是最古老的垃圾收集器,Serial 是一个单线程的垃圾收集器,它不仅仅是在进行垃圾回收的时候使用单线程,而是在整个垃圾回收过程中需要暂停所有的工作线程,也就是整个垃圾回收的过程都需要进行 STW(Stop The World),直到垃圾回收工作结束,Serial 垃圾收集器新生代使用标记-复制算法,老年代使用标记-整理算法,指定使用 Serial 垃圾收集器方式如下:
-XX:+UseSerialGC -XX:+UseSerialOldGC
Serial 垃圾收集器工作流程简图如下:
Serial 垃圾回收器的工作流程如上图所示,垃圾回收线程在工作的时候,所有工作线程都必须停止下来,直到垃圾回收结束,Serial 垃圾收集器的有缺陷如下;
优点:
- 简单高效,没有线程之间的交互开销,适合于单核CPU 的应用。
缺点:
- STW 时间较长,不适合大内存应用和多 CPU 环境。
Serial Old 垃圾收集器
Serial Old 垃圾收集器是 Serial 垃圾收集器的老年代版本,同样是一个单线程收集器,它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为CMS收集器的后备方案。
ParNew 垃圾收集器
ParNew 垃圾收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为和 Serial 收集器完全一样,新生代采用标记-复制算法,老年代采用标记-整理算法。
ParNew 垃圾收集器工作流程简图如下:
从简图可以看出除了垃圾回收时候使用的多线程之外,其他步骤和 Serial 垃圾收集器完全一致,同时除了 Serial 垃圾收集器之外,ParNew 垃圾收集器是唯一可以和 CMS 垃圾收集器配合使用的垃圾收集器,因为和 Serial 垃圾收集器高度相似优缺点就不在说明。
Parallel Scavenge 垃圾收集器
Parallel Scavenge 收集器类似于ParNew 收集器,是 Server 模式(内存大于2G,2个cpu)下的默认收集器,Parallel Scavenge 收集器关注点是吞吐量(CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值),Parallel Scavenge 垃圾收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,新生代采用的标记-复制算法,老年代采用的标记-整理算法。
Parallel Scavenge 垃圾收集器工作流程简图如下:
Parallel Scavenge 类似于ParNew 收集器,因此其工作流程也是类似。
Parallel Old 垃圾收集器
Parallel Scavenge 垃圾收集器的老年代版本,采用的是标记-整理算法,在注重吞吐量以及 CPU 资源的场景,可以优先考虑 Parallel Scavenge 垃圾收集器和 Parallel Old 垃圾收集器。
指定使用 Parallel Scavenge 垃圾收集器如下:
-XX:+UseParallelGC(新生代) -XX:+UseParallelOldGC(老生代))
CMS 垃圾收集器
CMS(Concurrent Mark Sweep)垃圾收集器是一种以获取最短回收停顿时间为目标的垃圾收集器,它而非常符合在注重用户体验的应用上使用,它是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作,CMS 垃圾收集器采用的是标记-清除算法。
CMS 垃圾收集器相对于前面四个垃圾收集器来说更复杂一些,共分为四个阶段,如下:
- 初始标记:暂停所有用户线程(STW),并记录所有直接与根(GC ROOTS)对象相连接的对象,这个阶段速度很快。
- 并发标记:同时开始用户线程和垃圾回收线程,用一个闭包去记录可达对象,但这个闭包不能保证所有的可达对象,因此工作线程还在工作中,会不断地更新对象的引用,垃圾回收线程无法保证保证可达性分析的实时性,因此算法会记录发生引用更新的对象(这个在前面分享垃圾回收算法的时候提到过)。
- 重新标记:重新标记这个阶段是 STW 的,重新标记就是为了处理在并发标记阶段发生引用变更的对象,因为发生变更的引用变更的对象毕竟是少数,因此 STW 的时间不会太长,远比并发标记阶段的时间短,可能会略长于初始标记阶段。
- 并发清除:用户线程开始工作,垃圾回收线程会开始进行垃圾回收。
为了方便对 CMS 垃圾回收器工作流程的理解,这里画一个工作流程简图如下:
CMS 垃圾回收器的优缺点
优点:可以进行并发垃圾收集,低停顿。
缺点:
- 对 CPU 资源敏感,因为是并发工作,会和用户线程抢 CPU 资源。
- 因为 CMS 是基于标记清除算法实现的,会产生内存碎片,降低了内存的利用率。
- 无法处理浮动垃圾,在 Java 业务程序线程与垃圾收集线程并发执行过程中又产生的垃圾,这种浮动垃圾只能等到下一次垃圾再清理。
G1 垃圾收集器
G1(Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多核心处理器及大容量内存的机器(4G 以上的服务器才建议使用 G1 垃圾回收器),结合多种垃圾收集算法,从整体上看是基于标记-整理算法,从局部来看是基于标记-复制算法,都不会产生空间碎片,可预测的停顿,可以指定垃圾收集的时间,以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能。
G1 垃圾回收器将堆划分为多个大小相等的独立区域(Region),虽保留新生代和老年代的概念,但不再是物理隔阂了,它们都是(可以不连续)Region的集合,Humongous 区专门存放短期巨型对象,这种大对象不用直接进老年代,可以极大程度避免 Full GC 的发生,也可以避免因为无法找到连续空间而提前触发下一次 GC 的情况发生。
G1 垃圾回收器内存示意简图如下:
G1 垃圾回收器工作流程分为以下价格阶段,如下:
- 初始标记:暂停所有用户线程(STW),并记录所有直接与根(GC ROOTS)对象相连接的对象,这个阶段与常规的 (STW) 年轻代垃圾回收密切相关。
- 并发标记:同时开启用户线程和 GC 线程标记处存活对象,耗时较长,其他应用程序也在运行,不能保证标记出所有存活的对象。
- 最终标记:为了修正并发标记期间因用户线程继续运行而导致标记变动的那一部分对象的标记记录,该过程需要 STW,且时间比初始标记时间长。
- 筛选回收:首先对各个 Region 的回收价值和成本进行排序,然后根据用户期望的 GC 停顿时间来指定回收计划,最后按计划回收一些价值高的 Region 中垃圾对象,回收时采用标记-复制算法,将一个或者多个 Region 存活对象复制到另一个Region,并且在此过程中压缩和释放内存,可以并发进行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率,因此没有使用并发进行。
G1 垃圾回收器工作流程简图如下:
G1 的由来:
G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region,这也就是 G1 名字 Garbage-First 的由来,这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了G1 垃圾收集器在有限时间内可以尽可能高的收集效率。
如何选择垃圾收集器
- 根据机器内存进行选择,如果内存比较小或者是单核机服务器,可以选择使用 Serial 来作为垃圾收集器。
- 根据业务类型选择,看业务是更在意 STW 还是吞吐量,如果更在意吞吐量可以选择 Parallel Scavenge 垃圾收集器,如果在意的 STW 就可以选择 G1 垃圾收集器。
- 根据服务器内存来选择,如果内存小于 4G 可以选择 CMS 垃圾收集器,当内存超过 4G 后才考虑使用 G1、ZGC 垃圾收集器。
- 根据 JDK 版本选择垃圾收集器,不同版本的 JDK 支持的垃圾收集器是不一样的。
部分垃圾收集器是需要配合工作的,下面我们列出可以搭配使用的垃圾收集器,如下:
查看垃圾收集器 windows 平台适用
java -XX:+PrintCommandLineFlags -version
执行结果:
C:\Users\Administrator>java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=534074176 -XX:MaxHeapSize=8545186816 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
可以看到 JDK8 默认使用的是 UseParallelGC 垃圾收集器。
总结:本篇简单分享了垃圾收集器的工作流程及原理,对我们理解 JVM 以及 JVM 内存调优有一定的帮助,希望可以帮助到正在提升的你。
如有不正确的地方欢迎各位指出纠正。