文章目录
- 垃圾收集器概述
- Serial 与 Serial Old 垃圾收集器
- Serial 与 Serial Old 垃圾收集器总结
- ParNew 垃圾收集器
- Parallel Scavenge 垃圾收集器
- Parallel Scavenge 的吞吐量控制参数
- Parallel Scavenge 的自适应调节策略
- Parallel Scavenge 垃圾收集器总结
- ParNew 和 Parallel Scavenge 垃圾收集器的对比
- Parallel Old 垃圾收集器
- CMS 垃圾收集器
- CMS 垃圾收集器的收集过程
- CMS 的缺点
- 处理器资源敏感
- Concurrent Mode Failure
- 浪费堆内存空间
- 没有很好地处理内存碎片
- 无法处理浮动垃圾
- G1 垃圾收集器
- G1 垃圾收集器管理下的堆内存布局
- 区域的主要特性
- G1 垃圾收集的过程
- G1 垃圾收集动作分类
- Young GC/Minor GC
- Mixed GC
- Full GC
- G1 垃圾收集器总结
垃圾收集器概述
可以用一句非常形象的话来描述垃圾收集器:
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
这里我们主要介绍基于分代收集理论设计的垃圾收集器
Serial 与 Serial Old 垃圾收集器
Serial
与 Serial Old
垃圾收集器,都是单线程收集的垃圾收集器。
其中 Serial
用于收集新生代,使用的是 Appel
式复制算法;SerialOld
用于收集老年代,使用的是标记整理算法。
他们的特点也非常明显,即都是单线程执行 GC
工作。
Serial 与 Serial Old 垃圾收集器总结
Serial
与 Serial Old
垃圾收集器优缺点如下:
优点:
- 实现简单
- 单线程收集效率较高
- 资源消耗低
缺点:
- 无法运用多核
CPU
多线程并行处理优势,导致整体工作效率低下
ParNew 垃圾收集器
ParNew
垃圾收集器,是一个支持多线程并行收集的新生代垃圾收集器,本质上是 Serial
的多线程并行版本。
ParNew 垃圾收集器,一个非常重要的特性就是除了 Serial 垃圾收集器外,只有它能跟 CMS 垃圾收集器搭配工作
ParNew
使用的也是 Appel
式复制算法
Parallel Scavenge 垃圾收集器
Parallel Scanvenge
垃圾收集器,也是一个支持多线程并行收集的垃圾收集器。
Parallel Scanvenge
垃圾收集器关注的是吞吐量,所以也被称为吞吐量优先收集器,吞吐量计算公式为:
吞吐量=运行用户代码时间运行用户代码时间+运行垃圾收集时间吞吐量=\frac{运行用户代码时间}{运行用户代码时间 + 运行垃圾收集时间} 吞吐量=运行用户代码时间+运行垃圾收集时间运行用户代码时间
运行用户代码时间 +
运行垃圾收集时间基本上约等于程序运行总时间,则由公式可以得出:
- 运行垃圾收集时间与程序运行总时间的
比值越小
,则吞吐量越高 - 运行垃圾收集时间与程序运行总时间的
比值越大
,则吞吐量越低
Parallel Scavenge 的吞吐量控制参数
Parallel Scavenge
提供了两个用于精准控制吞吐量的参数,分别是:
- 期望的最大垃圾收集停顿时间:
-XX:MaxGCPauseMills
,单位是毫秒- 当设置了这个参数之后,收集器将尽力保证单次垃圾收集花费的时间不超过设定好的参数值
- 在需要与用户频繁交互的场景下,垃圾收集停顿时间
越小
,则用户对于应用程序停顿的感知越少,即用户体验越好
- 此参数并不是越小越好,因为垃圾收集停顿时间的缩小是以牺牲吞吐量和新生代空间为代价来换取的
- 期望的最低吞吐量:
-XX:GCTimeRatio
- 当设置了这个参数后,收集器将会尽量保证吞吐量大于等于设定好的参数
- 默认值是
99
,即收集器将会尽量保证吞吐量大于等于99%
Parallel Scavenge 的自适应调节策略
Parallel Scavenge
提供了一个自适应调节策略开关参数:-XX+UseAdaptiveSizePolicy
。
当开启这个参数后,我们就不需要再手工指定新生代大小,Eden
和 Survivor
大小比例等细节参数了。虚拟机将会根据当前应用的运行情况和系统资源,动态调整这些参数来使垃圾收集的性能最优,让程序获得最高的吞吐量。
注意,设置这个参数同时也可以设置期望的最大垃圾收集停顿时间、期望的最低吞吐量参数,来给虚拟机设定一个优化目标。
Parallel Scavenge 垃圾收集器总结
Parallel Scavenge
是一款支持多线程并行收集的垃圾收集器,可以通过参数来精准控制吞吐量;也可以开启自适应调节策略,将虚拟机的一些运行细节参数交给虚拟机自行动态设置。
Parallel Scavenge
适用于计算密集型场景。
ParNew 和 Parallel Scavenge 垃圾收集器的对比
ParNew
和 Parallel Scavenge
都是支持多线程并行收集的垃圾收集器,它们之间的区别主要有以下几点:
Parallel Scavenge
可以精确控制吞吐量Parallel Scavenge
可以开启自适应调节策略ParNew
可以与CMS
搭配使用,Parallel Scavenge
不能
Parallel Old 垃圾收集器
Parallel Old
垃圾收集器,是一个支持多线程并行收集的老年代垃圾收集器。
Parallel Old
基于标记-整理算法实现,主要用于与 Parallel Scavenge
搭配使用。
JDK 8
默认的垃圾收集器组合就是 Parallel Scavenge
和 Parallel Old
。
CMS 垃圾收集器
CMS
垃圾收集器,全称 Concurrent Mark Sweep
垃圾收集器,是一种支持多线程并行清理,且能与用户线程并发执行的垃圾收集器。从字面意思来看,可以理解为并发标记清除垃圾收集器,也被称为并发低停顿收集器。
CMS
基于标记-清除算法实现。
CMS 垃圾收集器的收集过程
CMS
垃圾收集器的收集过程如下:
- 初始标记:执行
STW
,并标记下GC Root
能直接引用的对象。这个过程相当迅速 - 并发标记:退出
STW
,多个GC
线程从初始标记标记过的对象开始遍历对象的引用关系图- 此过程多个
GC
线程与用户线程一起运行,且耗时较长
- 此过程多个
- 重新标记:执行
STW
,并修正并发标记期间标记产生变动的那一部分对象的标记记录(采用增量更新的方式解决)- 在并发标记过程中,
GC
线程与用户线程同时在运行,所以很可能会导致一些已经标记过的对象的存活状态发生改变,故而需要STW
来修正这些变动的标记
- 在并发标记过程中,
- 并发清理:退出
STW
,GC
线程开始清理标记阶段判定为死亡的对象- 此过程多个
GC
线程与用户线程一起运行 - 清理完成后不需要移动存活对象,即没有整理内存碎片
- 此过程多个
- 并发重置:重置本次
GC
过程中的标记数据- 此过程
GC
线程与用户线程一起运行
- 此过程
整个过程如下所示:
CMS 的缺点
CMS
的优点非常明显,即并发收集与低停顿。与此同时,也有几个明显的缺点
处理器资源敏感
CMS
垃圾收集器在与用户线程并发执行的阶段中,虽然不会暂停用户线程,但是由于 GC
线程的执行本身就需要一部分系统资源,所以会抢占用户线程的系统资源,导致用户线程的执行变慢,从而降低总吞吐量。
这种情况在系统资源充足,处理器核心数比较多的场景中并不是很明显(但多少还是会有影响),但是一旦处理器核心数较少时(少于 4
个),(GC
线程对资源的占用导致应用程序变慢的情况)可能就会变得比较明显。
Concurrent Mode Failure
由收集过程我们可以知道,在并发标记和并发清理阶段,用户线程和 GC
线程是并发执行的。
那么在这两个阶段的实际的执行过程中,由于用户线程在同时执行,即用户线程在正常地调用、退出方法(创建和销毁栈帧),声明对象,或者修改引用关系,那么很可能出现再次触发 GC
的情况(即用户线程的这些操作又导致可用空闲不足或者触发了其他垃圾回收的条件)。
这种情况就是 Concurrent Mode Failure
,在这种情况发生后,会立即 STW
,使用 Serial Old
垃圾收集器来回收。这不是个好消息,因为 Seial Old
的收集效率比较低下。
即当出现 Concurrent Mode Failure
时,JVM
会使用 Serial Old
垃圾收集器进行回收,本次的停顿时间将会变得很长。
浪费堆内存空间
由于在执行 GC
的过程中,还需要让用户线程继续执行,所以触发 GC
的条件不能像其他老年代垃圾收集器一样,一直到快满了才触发,而是需要预留一部分空间,让执行 GC
的过程中并发执行的用户线程使用。
这个预留空间的大小(一个比例)通常可以配置,默认是 92
。即触发 GC
的条件是老年代的空间使用了 92%
,剩余的 8%
将预留给执行 GC
过程中并发执行的用户线程使用。
这个空间并不是越小越好,当执行 GC
过程中,这个预留的空间不够用而导致触发一次新的 GC
时,将会出现一次 Concurrent Mode Failure
。
所以,这里就需要有一个取舍:
- 如果这个预留空间设置得比较大,那么将会浪费内存空间
- 如果这个预留空间设置得比较小,那么可能会导致频繁出现
Concurrent Mode Faliure
,从而拉低吞吐量
没有很好地处理内存碎片
CMS
是一款基于标记-清除算法实现的垃圾收集器,在拥有标记-清除算法带来的优点的同时,不例外地,它也存在算法带来的缺点——没有整理内存碎片。
当内存碎片过多时,就会给大对象的内存分配带来麻烦:老年代还剩余很多内存空间,但是却找不到一块足够大的连续的内存空间,当出现这种情况时,虚拟机不得不发起一次 Full GC
来尝试获得一块足够大的连续的内存空间。
CMS
为解决这个问题,提供了两个解决方案:
- 开启
-XX:+UseCMSCompactAtFullCollection
参数,让虚拟机在执行Full GC
后执行一次碎片整理,开启后基本等同于标记-整理算法- 由于碎片整理需要移动存活的对象,所以这个过程是需要
STW
的,这会导致停顿时间变长,从而降低吞吐量
- 由于碎片整理需要移动存活的对象,所以这个过程是需要
- 设置
-XX:CMSFullGCsBeforeCompaction
参数,即进行多少次Full GC
后执行一次内存碎片整理- 例如当此参数的值设置成
3
的含义代表着,进行了3
次Full GC
后在下次(即第4
次)Full GC
执行前,先执行一次内存碎片整理后,再执行Full GC
- 默认值为
0
,即每次进行Full GC
之前都要先整理一遍内存碎片
- 例如当此参数的值设置成
无法处理浮动垃圾
如果在两个与用户线程并发执行的过程中,又有新的垃圾对象产生,那么这部分对象将变成浮动垃圾,则本次清理过程并不能处理掉这些对象。
很多时候,浮动垃圾带来了执行过程中的不确定性,例如很可能因为产生的浮动垃圾足够多而重新触发了一次新的 GC
过程,这就会引起 Concurrent Mode Failure
,从而使吞吐量下降
G1 垃圾收集器
G1
,即 Garbage First
垃圾收集器,它是一款面向服务端应用的垃圾收集器。在系统资源充足(大容量内存和 CPU
核心数较多)的情况下,可以同时满足低停顿与高吞吐量的要求。
G1 垃圾收集器管理下的堆内存布局
G1
将堆划分成了多个大小相等的独立区域(Region
),每个区域都可以根据当前内存分配需要而扮演不同的角色,例如扮演 Eden
空间,扮演 Survivor
空间,或者扮演老年代空间。
G1
可以根据每个区域当前扮演的角色不同,采用通过不同的策略进行管理。
区域的主要特性
- 区域最多为
2048
个 - 每个区域的大小取值范围为
1~32MB
,且必须为2
的幂次方,可以通过参数-XX:G1HeapRegionSize
来指定 - 区域有四种角色:
Eden
:表示这个区域当前是Eden
空间的一部分Survivor
:表示这个区域当前是Survivor
空间的一部分Old
:表示这个区域当前是老年代空间的一部分Humongous
:表示当前这个区域存储的是一个大对象(部分或全部,即一个大对象可能会存放在一个,或连续的几个Humongous
区域中)- 当一个对象的大小超过了区域大小的
50%
,那么就会被判定为大对象
- 当一个对象的大小超过了区域大小的
- 新生代、老年代的概念依然存在,但是大小不再固定,空间也并不连续
- 新生代是由当前堆中所有扮演
Eden
和Survivor
角色的区域的合集,这些区域并不一定连续,总大小也不固定 - 老年代是由当前堆中所有扮演老年代角色的区域的合集
- 新生代是由当前堆中所有扮演
G1 垃圾收集的过程
- 初始标记:进行
STW
,并标记GC Root
能直接引用的对象 - 并发标记:结束
STW
,多个GC
线程与用户线程并发执行,遍历上一步标记的对象的引用关系图,判定对象的存活状态 - 最终标记:进行
STW
,使用原始快照(SATB
)的方法处理上一步中对象引用关系存在变化的对象 - 筛选回收:进行
STW
,对需要进行回收的Region
进行回收价值和成本的排序,并结合用户期望的停顿时间来制定回收计划- 最终制定的回收计划中,需要回收的
Region
当前所扮演的角色可能是不同的,但是一定是当前最值得回收的一些区域 - 回收时,将会把这些
Region
中存活的对象全部复制到空的Region
中,再清空旧Region
的空间 - 用户期望的停顿时间对于决定最后要回收的回收集是相当重要的,如果这个值越小,那么可能最后要回收的回收集的数量也越少,档次回收效率也会变低
- 最终制定的回收计划中,需要回收的
G1 垃圾收集动作分类
Young GC/Minor GC
Young GC
(或者说 Minor GC
),回收范围为年轻代。当现有的所有的 Eden
区放满了不会立马触发,而是会估算现有的 Eden
区回收需要的时间,估算出来的值与用户指定的最大停顿时间进行对比:
- 如果估算出来的回收现有
Eden
区所需的时间远小于用户指定的最大停顿时间,那么将不会执行Young GC
,而是会增加年轻代的Region
- 如果估算出来的回收现有
Eden
区所需的时间接近于用户指定的最大停顿时间,那么将执行Young GC
Mixed GC
Mixed GC
,回收范围为所有区域,当老年代的区域占有率达到设定的参数值时触发。
使用复制算法,当执行拷贝发现已经没有足够可用的空 Region
区域时,将会触发一次 Full GC
Full GC
Full GC
,回收范围为所有区域。当 Mixed GC
执行中发现没有足够可用的空 Region
区域时触发。
Full GC
将会进行 STW
,然后采用单线程进行标记、清除、压缩整理,以便释放出新的空 Region
。
G1 垃圾收集器总结
G1
垃圾收集器的主要特点如下:
- 独特的堆内存划分方式
- 良好的内存碎片整理机制:
G1
整体是基于标记-整理算法来实现的,但是从局部来看是基于复制算法来看实现的 - 可预测的停顿时间模型:用户通过指定
-XX:MaxGCPauseMills
来确切地指定每次GC
的最大停顿时间