垃圾回收器
1. 分类
- 按线程数:串行垃圾回收器和并行垃圾回收器
串行回收器指同一时间段内只允许一个CPU用于执行垃圾回收操作,此时工作线程被暂停,直至垃圾收集工作结束。并行回收器指可以运用多个CPU同时执行垃圾回收,可以提升应用的吞吐量 。 - 按工作模式:并发式垃圾回收器和独占式垃圾回收器。并发式指回收器和应用程序线程之间交替执行,尽可能减少应用程序的停顿时间;独占式一旦开始运行就会停止应用程序中的所有用户线程,直到垃圾回收工作执行结束。
- 按碎片处理方式:压缩式垃圾回收器和非压缩式垃圾回收器
- 按工作区域:新生代垃圾回收器和老年代垃圾回收器
2. 性能指标
- 吞吐量:运行用户程序的时间占总运行时间的比例
- 垃圾收集开销:垃圾收集所用时间与总运行时间的比例
- 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间
- 收集频率:相对于应用程序的执行,收集操作发生的频率
- 内存占用:Java堆区所占内存的大小
- ……
其中最为重要的就是平衡吞吐量和暂停时间。如果以吞吐量优先,那么必然需要降低内存回收的执行效率,但这样会导致需要更长的暂停时间来执行内存回收;如果以低延迟优先为原则,那么为了降低每次执行内存回收时的暂停时间,就只能频繁的执行内存回收,这又引起了年轻代内存的缩减和导致程序吞吐量下降。
一般来说,在最大吞吐量优先的情况下,降低停顿时间。
2.1 吞吐量
吞吐量(throughput)指CPU用于运行用户程序的时间与CPU总消耗时间的比值,即
高吞吐量的程序有更长的时间基准,不太考虑快速响应。
2.2 暂停时间
暂停时间(pause time)指一个时间段内应用程序线程暂停,让GC线程执行的状态。
3. 概述
Java中已有的垃圾回收器有:
- 串行回收器:Serial、Serial Old
- 并行回收器:ParNew、Parallel Scavenge、Parallel Old
- 并发回收器:CMS、G1
按照垃圾回收器所在的内存区域可分为:
- 新生代回收器:Serial、ParNew、Parallel Scavenge
- 老年代回收器:Serial Old、Parallel Old、CMS
- 整堆回收器:G1
如上图所示,两个回收器之间有连线表示可搭配使用,其中实现表示现在仍可使用的组合,虚线表示已经弃用的组合,以最新版本的Java说明。
通过-XX:+PrintCommandLineFlags
查看命令行相关参数,然后使用jinfo -flag 相关垃圾回收器参数 进程ID
来查看此时JDK版本默认的垃圾收集器。
4. Serial回收器 - 串行回收
Serial回收器是最基本、历史最悠久的垃圾回收器,也是HotSpot中Client模式下默认的新生代垃圾回收器。Serial回收器使用复制算法、串行回收和STW机制的方式执行内存回收。
除了新生代之外,Serial Old用于执行老年代垃圾收集工作,它同样采用了串行回收和STW机制,只不过内存回收算法选择的是标记-整理算法。
Serial回收器的优点在于简单而高效,对于限定单个CPU的环境来说,Serial回收器由于没有线程交互的开销,可以获得最高的单线程收集效率。
HotSpot可以使用-XX:+UseSerialGC
参数可以指定新生代和老年代分别使用Serial GC和Serial Old GC。
5. ParNew回收器 - 并行回收
ParNew可以看做是是Serial回收器的并行版本,它同样用于新生代的垃圾回收,同时也是很多JVM在Server模式下新生代的默认垃圾回收器。
对于新生代,回收次数频繁,使用并行方式高效;对于老年代,回收次数少,使用串行方式节省资源。
可以使用XX:+UserParNewGCC
手动指定使用ParNew回收器执行内存回收任务,-XX:ParallelGCThreads
限制线程数量,默认开启和CPU数据相同的线程数。
6 . Parallel回收器 - 吞吐量优先
6.1 概述
Parallel Scanvenge回收器采用了复制算法、并行回收和STW机制执行新生代的垃圾回收工作。相比于ParNew而言,它的目标则是达到一个可控制的吞吐量,而且采用了自适应调节策略。对应的老年代的回收器为Parallel Old回收器,它采用了标记-整理算法,同时也采用了并行回收和STW机制。
在程序吞吐量优先的应用场景中,Parallel回收器和parallel Old回收器的组合在Server模式下的内存回收性能很优秀,因此,它也是Java8中默认的垃圾回收器。
6.2 相关参数
-XX:MaxGCPauseMillis
:设置垃圾回收器的最大暂停时间-XX:GCTimeRatio
:垃圾回收时间占总时间的比例,用于衡量吞吐量的大小,默认值为99-XX:+UseAdaptiveSizePolicy
:设置回收器具有自适应调节策略。在这种模式下,新生代的大小、Eden和Survivor的比例、晋升老年代的对象年龄等参数会被自动调整,已达到在堆大小、吞吐量和停顿时间之间的平衡点-XX:UseParallelGC
:手动指定新生代使用Parallel回收器-XX:+UseParallelOldGC
:手动指定老年代使用Parallel Old回收器>-XX:UseParallelGC
和-XX:+UseParallelOldGC
在Java中默认开启,两个参数互相激活。
-XX:ParallelGCThreads
:设置新生代垃圾回收器的线程数- 默认情况下,当CPU数量小于8个,它的值等于CPU的数量
- 当CPU数量大于8个,它的值等于3 + [5 * CPU_Count] / 8
7. CMS回收器
7.1 概述
CMS(Concurrent-Mark-Sweep)回收器第一次实现了垃圾收集线程和用户线程同时工作,它关注的重点是如何尽可能的缩短垃圾回收时用户线程的停顿时间。停顿时间越短,响应速度越快,用户体验越好。
CMS采用的是标记-清除算法,并且也会有STW。它作为老年代的垃圾回收器来用时。新生代的垃圾回收器只能从ParNew和Serial中二选一。
72. 工作原理
CMS的垃圾回收阶段分为4个主要的过程,即初始标记阶段、并发标记阶段、重新标记阶段和并发清除阶段:
- 初始标记阶段(Initial-Mark):程序所有的工作线程被STW所暂停,这个阶段仅仅只是标记出GC Roots能直接关联到的对象,一旦标记完成之后就会恢复之前被暂停的所有应用线程。由于直接关联对象比较小,因此速度很快
- 并发标记阶段(Concurrent-Mark):从GC Roots的直接关联对象开始遍历整个对象图的过程,耗时较长但不需要暂停应用线程,它们可以和垃圾回收线程并发执行
- 重新标记阶段(Remark):由于在并发标记阶段中程序的工作线程还会和垃圾回收线程同时执行或者交叉执行,因此因用户程序继续执行而导致标记产生变动的那一部分对象就会发生改变,因此需要重新标记的过程。它执行时间较长,但远比并发标记阶段耗时少
- 并发清除阶段(Concurrent-Sweep):清除标记阶段判断已经死亡的对象,并释放内存空间,它可以和用户线程同时并发执行
初始标记和重新标记的存在,使得CMS仍在存在STW。但并发标记和并发清除阶段不需要暂停其他的用户线程,因此整体的回收是低停顿的。
由于垃圾回收阶段用户线程并没有中断,所以在CMS回收过程中换应该确保用户线程有足够的内存可用。因此,CMS不是当老年代完全没有空间可用时才被触发,而是当堆内存的使用率达到一个阈值时,便开始进行回收,确保应用程序在CMS工作过程中依然有足够的空间支持应用程序运行。
如果CMS运行期间预留的内存空间不够用户程序使用,那么就会出现一次”Concurrent Mode Failure”失败,此时虚拟机将会临时启用Serial Old作为老年代的垃圾回收器使用,这样停顿时间就长了。
CMS GC 的优点:
- 并发收集
- 低延迟
不足之处有:
- 会产生内存碎片:标记-清除算法不可避免的会产生碎片,此时内存分配只能使用空闲列表法
- 对CPU资源非常敏感:在并发阶段时,它虽然不会导致用户线程暂停,但是也会因为占用一部分线程而导致应用程序变慢,总吞吐量下降
- 无法处理浮动垃圾:在并发标记阶段由于垃圾回收线程和用户线程是同时运行或交叉运行的,那么在并发标记阶段如果产生新的垃圾对象,CMS将无法对这些垃圾进行标记,最终会导致这些新产生的垃圾对象无法被及时回收,只能等到下一次GC时被回收
7.3 相关参数
-XX:UseConcMarkSweepGC
:手动指定使用CMS,此时的组合为ParNew+CMS+Serial OLD(后备方案)-XX:CMSInitiatingOccupanyFraction
:设置堆内存使用率的阈值,一旦达到该阈值,并开始进行回收,默认值为68-XX:+UseCMSCompactAtFullCollection
:用于指定在执行完Full GC后堆内存空间是否进行压缩整理。避免内存碎片的产生,它会导致停顿时间变长-XX:CMSFullGCsBefoerCompaction
:设置在执行完多少次Full GC后对内存空间进行压缩整理-XX:ParallelCMSThreads
:设置CMS的线程数,默认是(ParallelGCThreads + 3) / 4