G1 收集器是目前 Java 服务端主流的分代收集器之一,它最大的特点是将堆划分为多个 Region,并通过 Young GC、Mixed GC、Full GC 三种回收动作配合工作。
其中 Mixed GC 是 G1 独有的回收机制,也是很多同学容易混淆的知识点。本文将从定义、触发条件、完整流程、是否一定执行、与 Young GC/Full GC 的区别等角度,系统梳理 G1 Mixed GC 的核心逻辑。
一、什么是 G1 Mixed GC
1.1 定义
Mixed GC 是 G1 收集器独有的回收动作,Parallel / CMS 等收集器均没有该概念。
它的核心作用是:
分批回收老年代垃圾,尽量降低 Full GC 发生的概率。
1.2 回收范围
Mixed GC 的回收范围包括:
- 整个新生代:Eden + Survivor
- 部分老年代 Region:只挑选垃圾占比最高、回收收益最大的一批老年代 Region
需要特别强调:
Mixed GC 不会回收全部老年代 ,每次只回收一部分 Region。
虽然它同时回收新生代,但新生代回收本身普通 Young GC 就能完成,Mixed GC 的出发点是解决老年代垃圾堆积问题。
1.3 算法与停顿
- 算法:复制算法
- 停顿:全程 STW,但通常比 Full GC 停顿时间短得多
二、触发前提:必须先完成一整套并发标记周期
很多同学误以为"老年代达到 45% 就会触发 Mixed GC",其实这是不准确的。
2.1 IHO 阈值的作用
G1 通过参数:
-XX:InitiatingHeapOccupancyPercent
简称 IHO ,默认值为 45%。
它的含义是:
当老年代占用达到整个堆的 45% 时,触发并发标记周期,而不是直接触发 Mixed GC。
检测时机通常发生在 Young GC 的 STW 阶段内:
- Eden 满,触发 Young GC;
- Young GC 回收完新生代后,在 STW 尚未释放时检测老年代占用;
- 如果老年代占用超过 IHO 阈值,则启动并发标记周期;
- 如果未超过阈值,则只完成普通 Young GC,不启动标记周期。
因此:
- IHO 阈值是并发标记周期的开关;
- 并不是 Mixed GC 的开关;
- 只有完整跑完并发标记周期,才可能执行 Mixed GC。
2.2 未达阈值的情况
如果老年代占用始终低于 IHO 阈值:
- 只反复执行普通 Young GC;
- 不会启动并发标记周期;
- 老年代垃圾不会被扫描,也不会被回收;
- 自然也不会有 Mixed GC。
三、完整并发标记周期时序
当老年代占用达到 IHO 阈值后,G1 会启动一整套并发标记周期,主要包括以下 4 个阶段:
3.1 初始标记(Initial Mark,STW)
初始标记会挂靠在一次 Young GC 的 STW 上。
过程:
- Eden 满,触发 Young GC;
- Young GC 回收新生代;
- 在同一个 STW 中检查老年代占用超过阈值;
- 执行初始标记,标记 GC Roots 直接可达对象;
- STW 结束。
注意:
绝大多数 Young GC 不会执行初始标记。
只有老年代达到阈值的那一次 Young GC,才会复用其 STW 执行初始标记。
3.2 并发标记(Concurrent Mark,无 STW)
并发标记阶段,GC 后台线程与业务线程并行执行,遍历堆对象图,做三色标记,识别老年代存活对象与垃圾对象。
重点:
- 并发标记可以被 Young GC 打断;
- Eden 一旦满,就会触发 STW 的 Young GC,并发标记线程暂停;
- Young GC 执行完毕后,并发标记线程从断点继续执行;
- 使用 SATB(Snapshot At The Beginning)原始快照写屏障,记录 Young GC 复制、晋升带来的对象变动,防止漏标。
3.3 最终标记(Remark,STW)
最终标记阶段会处理 SATB 队列、RSet 记忆集等,修补并发标记期间,包括中途穿插的 Young GC 产生的全部漏标。
完成后,整个堆所有对象的存活状态基本标记完成。
3.4 筛选回收:Mixed GC(STW)
基于前面标记得到的老年代 Region 垃圾统计信息,挑选回收收益最高的一批老年代 Region,连同当前已经被业务重新填满的新生代,一起复制回收。
关键点:
当初开启标记周期的那次 Young GC 已经清空了新生代,但在并发标记和 Remark 期间,业务线程持续创建对象,Eden 又会被重新填满。
因此 Mixed GC 必须同时处理新生代。
完整时序可简化为:
Eden 满
↓
Young GC(清理新生代)
↓ 检测老年代超过 IHO?
├── 否 → 普通 Young GC 结束,不启动并发标记
└── 是 → 同一个 STW 执行初始标记
↓
并发标记(可被多次 Young GC 打断)
↓
Remark 最终标记
↓
Mixed GC(回收新生代 + 部分老年代 Region)
四、Mixed GC 一定执行吗?两种结局
即使老年代达到 IHO 阈值,启动了并发标记周期,Mixed GC 也不一定 100% 执行。
4.1 正常流程:成功执行 Mixed GC
如果标记周期顺利完成,G1 会根据标记结果分批执行 Mixed GC:
- 每次 Mixed GC 回收一部分老年代 Region;
- 释放内存,老年代占用下降;
- 后续对象继续晋升,老年代再次达到 IHO 阈值;
- 开启新一轮标记周期,再次执行 Mixed GC。
这样循环往复,尽量不发生 Full GC。
一次标记周期结束后,通常会触发多次 Mixed GC,每次只回收一部分老年代 Region,逐步释放空间。
4.2 Mixed GC 不会执行的两个场景
场景 1:老年代占用始终低于 IHO 阈值
- 不启动并发标记周期;
- 只会反复执行普通 Young GC;
- 老年代垃圾不会被清理;
- 不会执行 Mixed GC。
场景 2:对象晋升速度过快,老年代提前耗尽
- 已经启动并发标记周期;
- 但并发标记还未完成,或 Mixed GC 还没机会执行;
- 大量对象晋升,老年代 Region 被耗尽;
- 直接退化触发 Full GC;
- Mixed GC 被跳过。
通俗理解:
IHO = 45% 相当于警戒线。
老年代到 45%,拉起"垃圾清点小组"即并发标记周期,清点哪些老年代 Region 垃圾多。
清点全部结束后,才安排分批打扫,即 Mixed GC。
如果还没清点完,房间直接堆满了,就只能全屋大扫除,即 Full GC,分批打扫就不做了。
房间没到警戒线,清点小组根本不会上班。
五、Young GC / Mixed GC / Full GC 对比
| GC 类型 | STW | 回收范围 | 触发条件 | 核心特点 |
|---|---|---|---|---|
| Young GC | ✅ STW | 仅新生代 Eden + Survivor | Eden 占满 | 复制算法;只清理新生代;对象晋升老年代;不回收老年代垃圾;可以打断并发标记 |
| Mixed GC | ✅ STW | 新生代 + 部分老年代 Region | 完整并发标记周期结束后 | G1 独有;分批清理老年代;用于规避 Full GC;不会回收全部老年代 |
| Full GC | ✅ 长时间 STW | 新生代 + 老年代 + 元空间全部 | 老年代内存耗尽 | 最坏兜底;通常单线程整理;生产环境极力避免 |
六、高频误区与坑点
误区 1:Mixed GC 会紧跟在触发标记的那次 Young GC 之后立即执行
错误。
Mixed GC 不会马上执行。
在初始标记之后,还要经历并发标记、Remark,期间可能发生多次普通 Young GC。
只有完整标记周期结束后,才会执行 Mixed GC。
误区 2:并发标记期间不能发生 Young GC
错误。
Young GC 优先级更高,可以打断并发标记。
并发标记线程会被暂停,Young GC 执行完后从断点继续。
G1 通过 SATB 写屏障保证标记正确性。
误区 3:Mixed GC 只回收老年代
错误。
Mixed GC 必须同时回收新生代。
因为并发标记和 Remark 期间,业务线程不断产生新对象,Eden 会被重新填满。
所以 Mixed GC 的回收范围是:新生代 + 部分老年代 Region。
误区 4:Mixed GC 会一次性清空全部老年代
错误。
Mixed GC 每次只回收一部分老年代 Region,通常需要多次 Mixed GC 才能逐步释放老年代空间。
这也是 G1 能够控制停顿时间的关键设计之一。
七、总结
G1 Mixed GC 的核心逻辑可以概括为以下几点:
- Mixed GC 是 G1 独有,用于分批回收老年代,避免 Full GC。
- IHO 阈值默认 45% 是并发标记周期的开关,不是 Mixed GC 的开关。
- 只有完整跑完:初始标记 → 并发标记 → Remark,才可能执行 Mixed GC。
- 并发标记可以被 Young GC 打断,SATB 保证不漏标。
- Mixed GC 回收新生代 + 部分老年代 Region,不是全部老年代。
- 若对象晋升速度过快,老年代耗尽,会直接 Full GC,跳过 Mixed GC。
理解这些逻辑,就能准确回答 G1 Mixed GC 的触发条件、执行流程以及常见误区。