历代收集器
| 单线程 | 吞吐量优先 | 低延迟 | 大堆软实时 | 超低延时 | |
|---|---|---|---|---|---|
| 垃圾收集器 | Serial + Serial Old | Parallel Scavenge + Parallel Old | ParNew + CMS(Serial Old 兜底) | G1 | ZGC / Shenandoah |
| 核心算法 | 复制+标记-整理 | 复制+标记-整理 | 复制+标记-清除 | 复制 + 整体整理 | 着色指针 + 读屏障并发复制 |
对比
| CMS | G1 | |
|---|---|---|
| 核心算法 | 标记-清除 | Region间标记-复制,整体标记-整理 |
| 内存碎片 | 存在大量碎片,需要整理 | 几乎无碎片 |
| 漏标处理 | 增量更新(写后屏障): 破坏黑色引用白色 | 原始快照(写前屏障): 破坏引用被删除 |
| 停顿控制 | 整体不可控 | 通过 -XX:MaxGCPauseMillis控制 |
| 额外内存开销 | 一张卡表(约1%~3%) | 每个Region一个RSet(约10%~20%) |
| 适用堆大小 | 中小堆(4GB~6GB) | 大堆(6GB及以上) |
| 退化场景 | Concurrent Mode Failure / Promotion Failed 退化为Serial Old | Evacuation Failure 退化为Full GC JDK10前Serial Old,JDK10及之后Parallel Full GC |
CMS(Concurrent Mark Sweep)
核心运行阶段
- 初始标记(STW):仅标记GC Roots直接关联到的对象和年轻代指向老年代的对象,耗时极短。
- 并发标记:从GC Roots关联的对象开始,遍历标记整个对象图,耗时最长。
- 重新标记(STW):扫描在并发标记阶段因引用变化而重新变灰的对象,耗时比初始标记稍长。
- 并发清除:清理没有被标记的垃圾对象。
Concurrent Mode Failure
并发清除时,老年代的空闲空间不足以放下新分配的对象或发生新生代晋升但空闲空间不足(Promotion Failed)。
此时STW,并退化为使用单线程的 Serial Old 收集器进行传统的 Full GC。
缺点
- CPU敏感:并发标记和并发清除阶段,CMS默认启动的回收线程数为(CPU核心数 + 3) / 4,会占用部分CPU资源。
- 浮动垃圾:并发清除阶段,用户线程的业务逻辑还在产生新的垃圾对象,这些对象不会被清理,只能在下一次GC清理。因此不能满了才回收,必须预留空间给新对象。
- 内存碎片:CMS使用标记-清除算法,会产生大量内存碎片,可能导致Concurrent Mode Failure。
相关参数:
-XX:+UseConcMarkSweepGC:开启 CMS 收集器。-XX:CMSInitiatingOccupancyFraction:CMS触发的老年代内存占用率阈值。-XX:+UseCMSCompactAtFullCollection:在触发Full GC后进行内存碎片整理。-XX:CMSFullGCsBeforeCompaction:经历n次不压缩的Full GC后进行碎片整理,默认为0。
G1(Garbage-First)
JDK9开始,G1成为了默认的垃圾收集器。采用标记-复制算法,因此不易产生内存碎片。适用于内存较大的堆。
停顿时间模型
G1利用历史衰减平均值算法与衰减标准差,对下一次 GC 各个阶段的耗时进行实时数学建模与概率预测,将GC时间控制在-XX:MaxGCPauseMillis内。
- Young GC:G1会动态调整新生代大小,防止复制时间过长超时。
- Mixed GC:在新生代Region的基础上,选择性价比最高的Region。
内存区域
G1逻辑上依然保留了Eden、Suvivor、Old等分代概念,但物理上不再连续。
G1将堆内存划分为约2048个大小相等的区域(Region),每个Region的大小在1MB ~ 32MB间(必须是2的幂次方)。
Region除了可以是Eden、Suvivor、Old,还可以是Humongous Region。
Humongous Region:用于存放超过0.5个Region大小的超大对象,如果对象超过了一个Region的大小,会寻找连续的空白Region区作为Humongous Region区存放。
Remembered Set
Remembered Set记录了其他Region指向本Region的引用。
在扫描一个Region时,为了知道是否存在其他Region对该Region的引用,但又不能全堆扫描一遍,因此每个Region都维护一个Remembered Set,Young GC时只需扫描RSet + GC Roots。
当引用变更时,写屏障会将跨Region的引用记录到Card Table,再同步到RSet。
缺点:RSet占用内存较大,需要占用10%到20%的堆内存。
Collection Set
表示本次要回收的Region集合。
Young GC:所有的Young Region。
Mixed GC:所有的Young Region和由算法计算出的收益最高的几个Old Region。
回收流程
Young GC
触发条件:Eden区满。
过程(STW):
- 扫描GC Roots + RSet。
- 复制存活对象到Survivor或晋升到Old。
- 清空回收的Region,重新分配角色。
并发标记周期
触发条件:当整堆占用达到-XX:InitiatingHeapOccupancyPercent(默认 45%)时触发。
过程:
- 初始标记(STW):标记GC Roots直接可达对象。借用Young GC完成。
- 根区域扫描:扫描 Survivor 区中指向老年代的引用,必须在下一次Young GC触发之前执行完毕。
- 并发标记:从 GC Roots 遍历整个堆,标记存活对象。
- 最终标记(STW):处理SATB缓冲区里发生引用改变的对象。
- 清理:
- 统计Region的存活率,将都为垃圾对象的Region清空(STW)。
- 将完全清空的空闲 Region 重置并回收。
Mixed GC
触发条件:并发标记结束后进入。
STW,但受-XX:MaxGCPauseMillis(默认200ms)控制。
回收所有的Young Region和由性价比最高的几个Old Region,分多次完成,采用标记-复制算法。
Full GC
触发条件:分配失败、复制失败、并发标记失败等。
- JDK 8 及以前:单线程STW,一旦触发,几十 GB 的堆可能导致分钟级别的卡顿,体验极差。
- JDK 10 及以后:引入了 Parallel Full GC,Full GC 改为使用与 Young/Mixed GC 相同数量的并行线程(
-XX:ParallelGCThreads)来执行,大幅降低了发生 Full GC 时的停顿时间,但依然存在明显 STW。
相关参数
-XX:+UseG1GC:启用 G1。-XX:MaxGCPauseMillis:目标最大 STW 时间,默认为200ms。-XX:G1HeapRegionSize:Region大小,必须为2的幂。-XX:InitiatingHeapOccupancyPercent:并发标记启动的整堆占用阈值,默认为45%。