每日面试题13:垃圾回收器什么时候STW?
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 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对比
总结
STW是GC保证正确性的必要代价,但通过优化标记算法(如三色标记+SATB)和并发设计(如CMS的并发标记、G1的Region划分),现代GC已将STW时间控制在可接受范围内。理解不同回收器的STW阶段,有助于我们在实际开发中根据业务需求(如延迟敏感型或吞吐量优先型)选择合适的GC算法,并通过JVM参数调优(如-XX:+UseConcMarkSweepGC或-XX:+UseG1GC)进一步降低停顿时间。


