STW是什么?——深入理解JVM垃圾回收中的"Stop-The-World"
在Java程序运行过程中,JVM会通过垃圾回收(GC)自动管理内存,释放不再使用的对象以腾出空间。但你是否遇到过程序突然卡顿的情况?这可能与GC过程中的Stop-The-World(STW,全局停顿)有关。本文将围绕"GC何时STW"展开,重点解析CMS与G1回收器的STW机制,并结合三色标记法说明其必要性。
一、STW的本质与核心作用
Stop-The-World(STW) 是JVM在垃圾回收过程中,为保证内存回收的正确性和一致性,临时暂停所有应用线程(User Thread)执行的现象。简单来说,就是"世界停止了",只有GC线程在工作。
为什么需要STW?
垃圾回收的核心是准确区分存活对象与可回收对象。若应用线程在GC过程中继续运行,可能导致:
- 对象状态变更:应用线程可能修改对象的引用关系(如创建新对象、销毁旧对象、修改指针指向),导致GC标记结果失效;
- 数据不一致:若GC线程与应用线程同时操作同一块内存,可能引发竞态条件(Race Condition),破坏内存管理的准确性。
因此,STW是GC保证自身逻辑正确的"保护机制",但过长或频繁的STW会显著降低程序性能(尤其是对延迟敏感的应用)。
二、三色标记法:GC标记对象的"通用语言"
无论是CMS还是G1,GC标记阶段均基于三色标记法(Tri-Color Marking)实现。这是一种通过颜色标记对象存活状态的并发标记算法,三种颜色含义如下:
颜色 | 含义 |
---|---|
白色 | 未被GC线程访问过的对象,默认视为"可回收垃圾"(未被标记)。 |
灰色 | 已被GC线程访问过,但其引用的其他对象尚未处理(待遍历的"边界"对象)。 |
黑色 | 已被GC线程完整处理(自身标记为存活,且所有引用对象也已处理),确认"存活"。 |
标记过程的关键:
GC线程从GC Roots(如栈帧局部变量、静态变量、JNI引用等)出发,将直接关联的对象标记为灰色(初始阶段);随后递归处理灰色对象的引用,将其目标对象标记为灰色,自身升级为黑色(并发阶段)。最终未被标记为黑色的白色对象将被回收。
但三色标记法存在一个天然缺陷:若应用线程在标记过程中修改了对象的引用关系(如删除灰色对象到白色对象的引用),可能导致白色对象被错误回收(漏标)。因此,GC需要通过STW阶段修正这些变动。
三、CMS回收器的STW阶段解析
CMS(Concurrent Mark-Sweep,并发标记-清除)是早期的并发GC算法,目标是减少STW时间,适用于对延迟敏感的场景。其核心流程包含4个阶段,其中初始标记和重新标记需要STW,其余阶段与应用线程并发执行。
1. 初始标记(Initial Mark,STW)
- 目标:快速标记GC Roots直接关联的对象(即从GC Roots出发的第一层可达对象)。
- STW原因:需暂停所有应用线程,确保标记的准确性(避免应用线程在此时修改GC Roots的引用关系)。
- 耗时:非常短暂(通常仅毫秒级),因为仅标记直接关联对象。
2. 并发标记(Concurrent Mark,并发)
- 目标:从初始标记的灰色对象出发,递归遍历所有可达对象,将其标记为黑色(存活)。
- STW状态:与应用线程并发执行(不暂停)。
- 风险:若应用线程在并发标记期间修改了对象的引用关系(如删除灰色对象到白色对象的引用),可能导致部分存活对象被漏标为白色。
3. 重新标记(Remark,STW)
- 目标:修正并发标记阶段因应用线程运行导致的标记变动(如漏标、错标)。
- STW原因:需暂停应用线程,确保所有标记变动被正确处理(例如,通过"增量更新"算法记录并发期间的引用变更,重新扫描这些变更的对象)。
- 耗时:比初始标记长,但远短于完全串行的标记过程(通常为初始标记的数倍)。
4. 并发清除(Concurrent Sweep,并发)
- 目标:回收未被标记的白色对象(垃圾),释放内存空间。
- STW状态:与应用线程并发执行(不暂停)。
- 特点:CMS采用"标记-清除"算法,因此不会移动存活对象,可能导致内存碎片(长期运行后可能引发Full GC)。
CMS的STW总结:
CMS通过并发标记和清除大幅减少了STW时间,但初始标记和重新标记仍需短暂停顿。其STW总耗时通常在10ms~100ms级别(取决于堆大小和对象复杂度)。
四、G1回收器的STW阶段解析
G1(Garbage-First)是JDK9及以后默认的GC算法,设计目标是平衡吞吐量与延迟(支持设置最大停顿时间)。与CMS不同,G1将堆划分为多个独立的Region(默认2MB~32MB),并通过"标记-整理"思想优化内存布局。其核心流程同样包含4个阶段,但STW的触发逻辑与CMS有显著差异。
1. 初始标记(Initial Mark,STW)
- 目标:标记GC Roots直接关联的对象,并记录每个Region中"已存活对象"的数量(用于后续回收价值排序)。
- STW状态:与应用线程并发执行(仅标记GC Roots直接关联的对象,耗时极短)。
- 特点:G1的初始标记通常与Minor GC(年轻代GC)合并执行,进一步减少STW时间。
2. 并发标记(Concurrent Mark,并发)
- 目标:递归标记所有可达对象,统计每个Region的存活对象比例。
- STW状态:与应用线程并发执行(不暂停)。
- 优化:G1通过"SATB(Snapshot-At-The-Beginning)"算法记录初始标记时的对象快照,即使后续应用线程修改引用关系,也能通过对比快照修正标记(减少漏标)。
3. 最终标记(Final Mark,STW)
- 目标:处理并发标记阶段SATB快照中未被处理的变动(如新增的白色对象),确保标记结果最终准确。
- STW状态:与应用线程短暂并发(仅扫描SATB队列中的变更,耗时通常在1ms~5ms)。
4. 筛选回收(Live Data Counting and Evacuation,STW)
- 目标:根据各Region的存活对象比例和回收价值(存活对象越少、Region越小,回收价值越高),选择部分Region进行回收,并将存活对象移动到其他Region(整理内存)。
- STW原因:需暂停应用线程,确保存活对象移动过程的原子性(避免应用线程访问正在移动的对象)。
- 耗时:取决于需要回收的Region数量(若仅回收少量Region,STW可控制在10ms以内)。
G1的STW总结:
G1的STW主要集中在初始标记、最终标记和筛选回收阶段,但通过并发标记和SATB算法大幅缩短了单次STW的时间。由于G1支持"增量回收"(每次只回收部分Region),其平均STW时间可控制在用户设定的阈值内(如不超过200ms)。
五、CMS与G1的STW对比
维度 | CMS | G1 |
---|---|---|
STW阶段 | 初始标记、重新标记 | 初始标记、最终标记、筛选回收 |
单次STW时长 | 较长(重新标记可能达百毫秒级) | 更短(筛选回收可通过Region选择优化) |
内存碎片 | 标记-清除算法导致碎片积累 | 标记-整理算法避免碎片 |
适用场景 | 老年代小堆(<8GB),对延迟敏感 | 大堆(>8GB),需平衡吞吐量与延迟 |
总结
STW是GC保证正确性的必要代价,但通过优化标记算法(如三色标记+SATB)和并发设计(如CMS的并发标记、G1的Region划分),现代GC已将STW时间控制在可接受范围内。理解不同回收器的STW阶段,有助于我们在实际开发中根据业务需求(如延迟敏感型或吞吐量优先型)选择合适的GC算法,并通过JVM参数调优(如-XX:+UseConcMarkSweepGC
或-XX:+UseG1GC
)进一步降低停顿时间。