先来看看目前主流的垃圾收集器和他们的关系
介绍两个术语的区别
并行:多个垃圾收集线程并行工作,但此时用户线程仍处于工作状态
并发:用户线程与垃圾收集器线程同时执行(可能交替执行),用于线程仍在工作,垃圾收集程序运行于另一个CPU上。1Serial收集器
Serial收集器是最基本,发展历史最久的收集器,在早期的版本中,它是唯一的垃圾收集器。这是一个单线程收集器,“单线程”的意义不仅仅在于它只使用一个CPU或一条收集线程去收集垃圾,更重要的是在垃圾收集时,它将暂停其他所有的线程(Stop The World),直到它的收集结束。下图是Serial/Serail Old收集器的工作流程。对于新生代来说,将使用复制算法进行收集,对于老年代来说,将使用标记-整理算法进行收集。
Serial收集器看上去会因为Stop TheWorld而造成用户体验很不好,实际上它依然是Client模式下的主要收集器。因为它简单而高效,对于限定单CPU的环境来说,Serial收集器由于没有其他线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。在桌面应用场景中,收集几十兆到上百兆的新生代垃圾仅仅会暂停线程几十到一百多毫秒,只要不频繁发生,这种停顿是可以接受的。
2 ParNew收集器
ParNew收集器就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数、收集算法、StopTheWorld、对象分配规则、回收策略等于Serial收集器完全一致。它的工作过程如下图所示
3 ParallelScavenge 收集器
这个收集器也是面向新生代的收集器,而且采用复制算法的多线程收集器,它与其他几个的区别在于它不是在尽可能缩短线程停顿时间,它的目标是达到一个可控制的吞吐量。
吞吐量的含义是CPU用于用户代码时间与CPU消耗的时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机运行了100分钟,垃圾收集花掉了1分钟,那吞吐量就是99%。
Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量 分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的–XX:GCTimeRatio参数。
- MaxGCPauseMillis参数允许的值是一个大于0的毫秒数,收集器将可能的保证内存回收花费的时间不大于这个值。不过这个数值并不是越少越好,因为系统缩短收集时间是以牺牲吞吐量作为前提的。如果这个值很小,系统就会把新生代调小一些,比如收集300MB内存肯定比收集500MB内存要快,但是这样的话垃圾收集将更加频繁,原来10秒收集一次,每次停顿100毫秒,现在5秒收集一次,每次停顿70毫秒,吞吐量自然下降了很多。
-
GCTimeRatio参数的值是一个大于0小于100的整数,也就是垃圾收集占用的时间比率,它是吞吐量的倒数。如果设置为9,那就运行最大GC时间占用总时间的10% (1/1+9),这个值默认是99,即1%。
4 SerialOld收集器
他是Serial 收集器的老年代版本,它同样是一个单线程收集器,使用“标记整理算法”。这个收集器主要给Client模式下虚拟机使用,如果在Sever模式下,它也有两个用途,一个用途是在JDK1.5版本以前与Parallel Scavenge收集器搭配使用,另一个是作为CMS的备案,在并发收集 Concurrent Mode Failure时使用。
5 Parallel Old 收集器
Parallel Old是Parallel Scavenge的老年代版本,使用多线程“标记-整理”算法,这个收集器开始于JDK1.6版本,它的出现使得Parallel收集器组合真正的完成。在注重吞吐量已经CPU资源敏感的场合,都优先考虑Parallel Scavenge和Parallel Old组合收集器。工作状态图:
6 CMS收集器
CMS(concurrent mark sweep)收集器是一种以获取最短停顿时间为目标的收集器。特别对于web应用来说,这个性能尤为重要。系统停顿时间越短,用户获得的体验就越好。CMS收集器是基于标记-清除算法的,它的实现需要四个步骤
- 初始标记(CMS initial mark)
- 并发标记(CMS concurrent map)
- 重复标记 (CMS remark)
- 并发清除 (CMS concurrent sweep)
其中,初始标记、重新标记这个两个步骤依然需要“Stop the world”。初始化标记仅仅是标记一下GCRoots能直接关联到的对象,速度很快,并发标记阶段就是进行GCRootsTracing的过程,而重新标记是在寻找并发标记期间因用户动作而导致标记产生变动的那一部分记录,这个停顿要比初始标记长一些,但远比并发标记的时间短。而整个过程耗时最长的并发标记和并发清除两个过程都是收集器线程与用户线程一起工作的,所以从总体上来讲,CMS收集器的内存回收过程是与用户线程一起并发执行的。
看上去CMS挺好,似乎解决了GC停顿时间过长的问题,然而它也存在如下问题:
CMS收集器对CPU资源十分敏感,在并发阶段,会因为它的存在导致用户的线程资源被占用,总吞吐量被降低。
CMS无法收集浮动垃圾,所谓浮动垃圾就是并发清除阶段用户的活动产生的垃圾,他们没有被标记也就无法被清除,只能等待下一次GC。
还有一个缺点就是CMS采用的标记-清除的算法,这种算法会导致产生很多不连续的内存碎片,往往老年代中还有很多空间,但是却不得不提前触发一次FullGC。为了解决这个问题,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开关参数,用于在CMS收集器要进行FullGC时开启内存碎片合并整理过程,内存整理的过程是无法并发的,因此停顿时间会增加;对于这话情况,虚拟机设计者提供了另外一个参数:-XX:CMSFullGCsBeforeCompaction,这个参数指定了执行多少次不带压缩的FullGC后,执行一次带压缩的。7 G1收集器
G1收集器在JDK1.7版本以后被正式启用,它是一款面向服务端应用的垃圾收集器,它的目的是在未来的版本中取代CMS收集器。
Garbage first垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与CMS收集器,G1收集器两个最突出的改进是:
a.基于标记-整理算法,不产生内存碎片。
b.可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。
G1收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾最多的区域,这也是它为什么叫garbage first的原因。
区域划分和优先级区域回收机制,确保G1收集器可以在有限时间获得最高的垃圾收集效率。