JVM垃圾回收器
1、GC垃圾回收器分类
- 按线程数量区分:串行垃圾回收器和并行垃圾回收器
并行垃圾回收器:是通过多个垃圾回收线程并行执行,可使用多个CPU同时执行垃圾回收操作,提升了应用吞吐量,但是和串行回收一样,都会遵循“Stop The World”机制。
串行垃圾回收器:是单线程执行垃圾回收,且只允许有一个CPU用于垃圾回收操作,此时用户线程会暂停,直至垃圾收集工作结束。串行和并行的区别在于在垃圾回收期间内,用户暂停的时间长短差异。如图:
- 按工作模式区分:并发式垃圾回收器和独占式垃圾回收器
并发式垃圾回收器:可以和应用程序线程交互工作,以最大限度减少应用程序的停顿时间。
独占式垃圾回收器:一旦运行,就会停止应用程序中所有的用户线程,直到垃圾回收完全结束。如图:
- 按碎片处理方式区分:压缩式垃圾回收器和非压缩式垃圾回收器
压缩式垃圾回收器会在回收完成以后,对内存中存活对象进行压缩整理,然后消除回收的碎片。
非压缩式垃圾回收器,不对存活对象进行整理操作。 - 按工作的内存区间分:年轻代垃圾回收器和老年代垃圾回收器
2、7款经典垃圾收集器
-
串行回收器:Serial、Serial Old
-
并行回收器:ParNew、Parallel Scavenge、Parallel Old
-
并发回收器:CMS、G1
-
垃圾回收器在年轻代和老年代中的关系:
-
垃圾收集器的组合:
- 两个收集器之间有连线,说明它们可以搭配使用:
Serial/Serial Old、Serial/CMS、ParNew/Serial Old、ParNew/CMS、Parallel Scavenge/Serial Old、Parallel Scavenge/Parallel Old、G1
- 其中Serial Old是作为CMS出现"Concurrent Mode Failure"失败后的预案。
- (红色虚线)由于维护和兼容性测试成本,在JDK 8已经将Serial+CMS、ParNew+Serial Old这2个组合声明废弃,在JDK9已经完全移除该组合。
- (绿色虚线)JDK 14中,已经弃用了Parallel Scavenge和SerialOld GC组合
- (青色虚线)JDK 14中,已经删除CMS垃圾回收器
3、垃圾回收器详解
3.1、Serial回收器:串行回收
- Serial收集器是最基本的垃圾收集器,在JDK 1.3之前回收新生代的唯一选择。
- Serial收集器作为HotSpot中Client模式下的默认新生代垃圾h收集器。
- Serial收集器采用复制算法、串行回收和“Stop-the-World”机制的方式进行内存回收。
- 除了年轻代,Serial收集器还提供用于老年代垃圾收集的Serial Old收集器。Serial Old 收集器同样采用串行回收和“Stop the World”机制,内存回收算法采用的是标记-压缩算法。
- Serial Old 是运行在Client模式下默认的老年代垃圾回收器
- Serial Old 在Server模式下主要有2个用途:①和新生代的Parallel Scavenge配合使用 ②作为老年代CMS收集器的后备垃圾收集方案。
如图,Serial、Serial Old回收器垃圾回收过程:
Serial收集器作为一个单线程的垃圾回收器,说明它只能使用一个cpu或一条收集线程进行垃圾收集工作,而且它必须暂停其他所有的工作线程,直到它收集工作结束。
优点:没有线程的交互开销,专心垃圾收集,获取最高的单线程收集效率。
缺点:暂停工作线程,系统容易出现卡顿。
3.2、ParNew回收器:并行回收
-
ParNew收集器是Serial收集器的多线程版本,可以有多个垃圾回收线程同时运行进行回收,这样Stop The World的世界会更短,但实际上用户线程同样需要暂停。
-
ParNew收集器在年轻代中,采用的也是复制算法、"Stop The World"机制。
-
ParNeW是很多JVM运行在Server模式下新生代的默认垃圾收集器。
- 对于新生代而言,回收次数频繁,使用并行方式高效、
- 对于老年代而言,回收次数少,使用串行方式节省资源。
- ParNew收集器是基于并行回收,其回收效率也不一定会比Serial收集器要高。ParNew是运行在多CPU环境下,可以充分利用多CPU、多核心的硬件资源优势,更加快速地完成垃圾收集,提升程序的吞吐量。
- 但是在单个CPU环境下,ParNew收集器不比Serial收集器更高效,因为Serial是基于串行回收的,不需要频繁地在cpu上做任务切换,可以有效避免多线程交互而产生的额外开销。
- 除了Serial以外,ParNew GC还可以和CMS收集器配合工作
- 可以通过
-XX:+UseParNewGC
手动指定使用ParNew收集器进行执行内存回收任务。它代表的是年轻代使用并行收集器,不影响老年代。 -XX:ParallelGCThreads
限制线程数量,默认开启和CPU数据相同的线程数。
3.3、Parallel Scavenge回收器:吞吐量优先(Java 8默认的垃圾收集器)
- Parallel Scavenge收集器采用复制算法、并行回收和"Stop The World"机制
- 可控制吞吐量,可以自行调节参数来控制以及一个自适应的策略。
- 和ParNew收集器不同,Parallel Scavenge收集器的目标是为了达到一个可控制的吞吐量。
- Parallel收集器在JDK 1.6时提供了用于执行老年代垃圾收集的Parallel Old收集器,用来代替老年代的Serial Old 收集器。
- Parallel Old 收集器采用的是标记-压缩算法,但是同样也是基于并行回收和"Stop The World"机制。
- 参数配置:
- -XX:+UseParallelGC 手动指定年轻代使用Parallel并行收集器执行内存回收任务。
- -XX:+UseParallelOldGC 手动指定老年代都是使用并行回收收集器
分别适用于新生代和老年代,在jdk 8中默认开启。上面2个参数,默认开启一个,另一个也会被开启(互相激活) - -XX:ParallelGCThreads 设置年轻代并行收集器的线程数。一般来说,最好和CPU的数量相等,避免过多的线程数量影响垃圾收集性能。
默认情况下,当CPU数量小于8,ParallelGCThreads的值等于cpu数量。当CPU的数量大于8时,ParallelGCThreads的值等于3+[5*cpu_count/8] - -XX:MaxGCPauseMillis 设置垃圾收集器最大停顿时间(即STW的时间,单位是毫秒)
为了尽可能把停顿时间控制在MaxGCPauseMillis以内,收集器在工作时会调整Java堆大小或者其他参数。而对于用户来说,该停顿时间越短,体验越好,但是在服务器端,更加注重高并发和整体的吞吐量。所以服务器端适合Parallel,进行控制。使用该参数需要谨慎!!!! - XX:GCTimeRatio 垃圾收集时间占总时间的比例,用于衡量吞吐量大小。
取值范围(0-99),默认值99,也就是垃圾回收时间不超过1%。与前一个-XX:MaxGCPauseMillis参数有一定矛盾性,暂停时间越长,Radio参数就容易超过限定的比例 - -XX:+UseAdaptiveSizePolicy 设置Parallel Scavenge收集器具有自适应调节策略
在该模式下,年轻代的大小、Eden和Survivor的比例、晋升老年代的对象等参数会被自动调整,已达到在堆大小、吞吐量和停顿时间之间的平衡点
在手动调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标的吞吐量(GCTimeRatio)和停顿时间MaxGCPauseMillis,让虚拟机自己完成调优工作
Don't forget the beginner's mind