JVM GC算法、GC收集器简介

原创
2018/03/01 14:36
阅读数 3.4K

一些术语:

  • STW:Stop The World,指GC回收时暂停所有用户线程的现象

  • 并发:指GC线程与用户线程并发执行,不会产生STW

  • 并行:指GC线程是多线程并行执行,会产生STW

GC算法


标记-清除算法

分为标记和清除两个阶段(Mark-Sweep),这个算法是最基础的收集算法,先标记处所有需要回收的对象,标记完成后统一回收。主要存在的不足之处有两点

  • 效率问题:标记和清除两个过程的效率都不高

  • 空间问题:被标记回收的对象所在的内存位置是不连续的,因此在清理之后剩余空间也是不连续的,会产生空间碎片,导致后续不能分配连续空间而提前触发另一次垃圾回收

执行过程的示意图如下:

标记-清除算法示意图

复制算法

复制算法主要是将内存划分为容量相等的两块,每次只使用一块,需要回收的时候,将还需要存活的对象复制到另外一块内存当中,然后把原使用的那块内存直接清理掉,根据这种算法的方式,可以分析出其优缺点

优点:

  • 复制的时候是将对象挨个放入到空的内存块中,所以不会有空间碎片的问题

  • 实现起来较为简单,运行起来比较高效

缺点:

  • 内存空间的利用率不大,因为始终要保持一半内存的空闲状态

适用于低内存空间(因为利用率只有50%)、存活对象较少(复制效率会更高)的场景

复制算法示意图

标记-整理(-清除)算法

总体上与标记-清除算法一样,只是多了一个内存整理的过程,主要是为了解决上面提到的标记-清除算法引起的空间碎片的问题

标记-整理算法示意图

分代收集算法

分代收集算法并没有新的算法收集方式,只是将内存划分为新生代和老年代,对不同分代采取不同的垃圾收集算法,综合不同分代的特性以提高垃圾收集的效率。比如年轻代的特点是对象存活率低,即大量对象都会被回收,因此比较适合采用复制算法;而老年代的特点是对象存活率高,,所以比较适合采用标记-清理或标记-整理算法

GC策略


Serial

Serial收集器是发展最久的垃圾回收器,Serial单次本身的意思是“串行”,是一个单线程的收集器,作用于新生代,采用复制算法,在进行垃圾回收的时候会产生STW,主要适用于单核CPU的场景,可以避免多线程上下文切换带来的消耗

Serial收集过程示意图

对于Serial收集器有个疑问,在《深入理解Java虚拟机-JVM高级特性与最佳实践》第2版第76页有如下描述:

Serial收集器是最基本、发展历史最悠久的收集器,曾经(在JDK1.3.1之前)是虚拟机新生代收集的唯一选择。大家看名字就会知道,这个收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。

这里描述的Serial可能并不是只有一个垃圾收集线程,也就是说明可能是多线程,但是在ParNew收集器中又说ParNew是Serial收集器的多线程版本,感觉两处有些矛盾,因为没看过源码,所以不知道具体是怎样的情况。如有知情者请告知,万分感谢

Serial Old

Serial的老年代版本,即与Serial一样,采用单线程垃圾回收,不同的是采用的标记-整理算法

Serial Old收集过程示意图

ParNew

ParNew是Serial的多线程版本,即使用多线程进行垃圾回收,作用于新生代,采用复制算法,进行垃圾回收时会产生STW

ParNew收集过程示意图

Parallel Scavenge

Parallel Scavenge与ParNew类似,只是关注点不同。Parallel Scavenge主要关注吞吐量,吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)。提供了两个参数来控制吞吐量

  • -XX:MaxGCPauseMillis 控制最大垃圾收集停顿时间,大于0的毫秒数

  • -XX:GCTimeRatio 设置吞吐量大小,大于0且小于100的整数,即垃圾收集时间占总时间的比率

Parallel Old

Parallel Old是Parallel Scavenge的老年代版本,多线程收集,使用标记-整理算法

CMS

以获取最短回收停顿时间为目标,作用于老年代,使用标记-清除算法,会产生空间碎片,但可以通过配置 -XX:+UseCmsCompactAtFullCollection (默认已开启)用于在CMS收集器要进行FullGC时开启内存碎片的整理

标记-清除的过程如下:

  • 初始标记:只标记GC Roots直接关联到的对象,速度快,会产生STW

  • 并发标记:根据GC Roots标记的对象进行追踪,标记出需要回收的对象。过程较长,但GC线程与用户线程并发执行,不产生 STW

  • 重新标记:修复并发标记过程中用户程序的运行导致标记的对象产生变动的的那部分对象,会产生STW

  • 并发清除:清除标记的对象,与用户线程并发执行,不产生STW

CMS运行过程示意图

G1

G1回收器除了综合其他回收器的回收方法之外,还将Java堆内存划分为很多个大小相等的独立区域Region,对新生代老年代也没有物理上的划分了,都是多个Region的集合。因此G1是作用在新生代和老年代的。从整体上看是使用的标记-整理算法,也避免了空间碎片的问题,从Region局部来看也有复制算法

  • 初始标记:只标记GC Roots直接关联到的对象,速度快,会产生STW

  • 并发标记:根据GC Roots标记的对象进行追踪,标记出需要回收的对象。过程较长,但GC线程与用户线程并发执行,不产生 STW

  • 最终标记:与CMS的重新标记类似,都是为了修复并发标记过程中用户程序的运行导致的变动的对象

  • 筛选回收:对各个Region的回收价值和回收成本进行排序,根据配置的期望GC停顿时间来决定回收计划,会产生STW,但也可以做到与用户程序并发执行

G1回收过程示意图

总结


GC回收器总结

回收器名称 使用算法 并发/并行 适用CPU场景 新生代 老年代 STW 其他
Serial 复制算法 并行 单核 × 整个GC过程会STW
Serial Old 标记-整理算法 并行 单核 × 整个GC过程会STW 此回收器同时也是CMS的备用回收器
ParNew 复制算法 并行 多核 × 整个GC过程会STW Serial的多线程版本,有很大部分公用代码
Parallel Scavenge 复制算法 并行 多核 × 整个GC过程会STW 与ParNew类似,只是更关注吞吐量,可通过配置控制吞吐量
Parallel Old 标记-整理算法 并行 多核 × 整个GC过程会STW Parallel Scavenge的老年代版本
CMS 标记-清除算法 并发+并行 多核 × 初始标记和重新标记会STW 共3次标记,需要注意空间碎片。回收失败后会采用Serial Old备用回收器
G1 标记-整理算法、复制算法 并发+并行 多核 初始标记和最终标记会STW 共3次标记,多Region,可通过配置控制吞吐量

GC组合总结

根据前面的介绍可以看出:

  • 新生代收集器:Serial、ParNew、Parallel Scavenge

  • 老年代收集器:Serial Old、Parallel Old、CMS

  • 整堆收集器:G1

可能的组合情况如下:

GC回收器组合场景

各个组合说明如下:

新生代策略 老年代策略 说明
Serial Serial Old Serial和Serial Old都是单线程进行GC,会产生STW,适用于单核场景
Serial CMS + Serial Old 当CMS进行GC失败时,会自动使用Serial Old策略进行GC
ParNew CMS + Serial Old 如果指定CMS回收器,则新生代会默认使用ParNew回收策略
ParNew Serial Old -XX:+UseParNewGC开启,新生代使用ParNew回收器时,老年代默认使用Serial Old回收策略
Parallel Scavenge Serial Old Parallel Scavenge适用于后台持久运行的应用程序,如跑批应用
Parallel Scavenge Parallel Old 两者都是并行回收策略
G1 G1 需要留意G1的稳定性

参考资料


文中截图均来自《深入理解Java虚拟机-JVM高级特性与最佳实践》第2版

展开阅读全文
打赏
2
22 收藏
分享
加载中
更多评论
打赏
0 评论
22 收藏
2
分享
返回顶部
顶部