前言
很多人的博文里面会提到JVM
三种垃圾收集器:串行,并行和并发。这么分类也不能说错,但事实上也没有这么简单。本文简要介绍Hotspot
早期的垃圾收集器(Garbage Collector
)。
名词解释
- 年轻代和老年代:
JVM
中不同的对象,生命周期是不同的。比如线程中的临时变量,在方法结束之后便会被销毁;而静态变量,则会一直存在直到其所对应的类被销毁为止。基于这样的事实,才有了分代收集的概念:将JVM
堆分为年轻代和老年代。年轻代存放刚被创建的对象,老年代存放经过多次GC
之后依然存活的对象。一般年轻代空间比较小,垃圾收集速度快,触发次数频繁,Minor GC
就发生在年轻代;老年代空间比较大,垃圾收集速度慢,触发频率低,Full GC
会包含老年代的回收。 - Stop-The-World(STW):垃圾收集器工作的时候,为了防止与应用程序线程相互干扰,会暂停应用线程。所以对于程序来说,垃圾收集时就像时间被停止了一般。
- 标记-复制算法(copying):垃圾收集器的收集策略,将存活的对象标记出来,统一复制到一块连续的内存之中。这种算法要求有一块足够大的空间可以放下所有存活的对象。
- 标记-清除算法(mark-sweep):垃圾收集器的收集策略,将存活的对象标记出来,然后清除掉死亡的对象。在连续的内存中清除掉一部分死亡对象之后,会导致内存碎片的产生。
- 标记-清除-压缩算法(mark-sweep-compact):垃圾收集器的收集策略,在标记-清除算法之外,将存活的对象进行压缩,可以想象成将对象往内存的一边移动以防止内存碎片。
各收集器简介
上面这张图参考自Oracle
官博,每个小方框代表了一种收集器,可以看到,早期的收集器有六种,三个在年轻代,三个在老年代。收集器之间的连线表示了,它们之间是否可以一起工作。
- 老年代串行(Serial Old):单线程的收集器,采用标记-清除-压缩算法,会
STW
。 - 老年代并发(CMS):从名字
Concurrent Mark Sweep
看出,它采用标记-清除算法,大部分时间和应用程序线程并发,极短的时间会STW
。 - 老年代并行(Parallel Old):多线程的收集器,采用标记-压缩算法,会
STW
。 - 年轻代串行(Serial):单线程的收集器,采用标记-复制算法,会
STW
。 - 年轻代并行(Parallel Scavenge):多线程的收集器,采用标记-复制算法,会
STW
。 - 年轻代并行(ParNew):多线程的收集器,采用标记-复制算法,会
STW
。它与Parallel Scavenge
的区别是,对CMS
收集器做了兼容,如在适当的地方加了线程间的同步处理。
收集器与JVM参数
可以用JVM
启动参数来显示指定选择何种收集器。
- -XX:+UseSerialGC:年轻代是串行(Serial),老年代是串行(Serial Old)。
- -XX:+UseParNewGC:年轻代是并行(ParNew),老年代是串行(Serial Old)。
- -XX:+UseConcMarkSweepGC:年轻代是并行(ParNew),老年代是并发(CMS)和串行(Serial Old)。
- -XX:+UseParallelGC:年轻代是并行(Parallel Scavenge),老年代是串行(Serial Old)。
- -XX:+UseParallelOldGC:年轻代是并行(Parallel Scavenge),老年代是并行(Parallel Old)。
注意:
- 老年代的串行收集器(Serial Old)可以和任意一个年轻代的收集器一起工作。
UseSerialGC
,UseParNewGC
和UseParallelGC
都只指定了年轻代的收集器,老年代默认还是用的串行收集器(Serial Old)。 - 为什么打开
-XX:+UseConcMarkSweepGC
之后,老年代会有两种收集器?前面有说到,CMS
采用的标记-清除算法会产生内存碎片,所以当内存碎片化到一定程度之后可能导致无法继续分配新的对象(从年轻代promote上来的对象),此时会触发一次串行的Full GC
(带压缩),通常会导致应用程序长时间的停顿。至于为什么不退化成并行收集器(Parallel Old),只能说开发者偷懒了... -XX:+UseConcMarkSweepGC
和-XX:+UseSerialGC
不能一起使用,否则会报收集器冲突的错。
总结
本文简要介绍了所谓的串行(Serial),并行(Parallel)和并发(CMS)收集器,以及相应的启用参数。因为目前还有很多公司还在用JAVA8
甚至之前的版本,所以了解它们还是很有必要的。