G1-并发标记和最终标记算法
一、直接答案精确版阶段使用的算法说明并发标记三色标记Tri-color Marking遍历对象图的算法将对象标记为黑/灰/白并发标记的底层支撑SATBSnapshot-At-The-Beginning写屏障Write Barrier保证并发标记的正确性最终标记处理SATB队列 重新扫描不是独立算法是SATB的收尾环节一句话总结并发标记用的是三色标记算法依靠SATB机制支撑最终标记是SATB的收尾处理两者共同构成一个完整的标记周期。二、三色标记算法详解并发标记的核心1. 三种颜色的含义颜色含义状态白色未被标记的对象不可达 → 最终会被回收灰色已被标记但其引用的对象尚未全部扫描正在处理中工作队列黑色已被标记且其引用的对象已全部扫描存活处理完成2. 算法执行流程用代码演示假设有以下对象图GC Roots → A → B → C ↘ DStep 1初始状态所有对象白色颜色状态 A: 白色 B: 白色 C: 白色 D: 白色 工作队列[]Step 2从GC Roots开始标记A和D为灰色A: 灰色已标记但未扫描其引用 B: 白色 C: 白色 D: 灰色 工作队列[A, D]Step 3从工作队列取出A扫描其引用BB从白色 → 灰色A的所有引用扫描完成 → A从灰色 → 黑色A: 黑色 B: 灰色 C: 白色 D: 灰色 工作队列[D, B]Step 4取出DD没有引用 → D从灰色 → 黑色A: 黑色 B: 灰色 C: 白色 D: 黑色 工作队列[B]Step 5取出B扫描其引用CC从白色 → 灰色B的所有引用扫描完成 → B从灰色 → 黑色A: 黑色 B: 黑色 C: 灰色 D: 黑色 工作队列[C]Step 6取出CC没有引用 → C从灰色 → 黑色A: 黑色 B: 黑色 C: 黑色 D: 黑色 工作队列[] 标记完成结果所有对象都是黑色 → 全部存活。如果某个对象最终仍为白色 → 判定为垃圾 → 回收。三、并发标记的问题为什么需要SATB问题场景并发标记时应用线程修改引用标记开始前快照 GC Roots → A → B ↘ C 标记进行中 - 应用线程执行A.next null切断A→B - 同时执行C.next BC→B建立新引用如果不做任何处理B会被标记为白色死亡→ 错误回收因为虽然A不再引用B但C现在引用了B。SATB的解决方案写屏障 快照// G1写屏障的伪代码voidwrite_barrier(Objectobj,Objectfield,Objectnew_value){// 将即将被覆盖的老引用保存到SATB队列Objectold_valueobj.field;if(old_value!nullis_white(old_value)){// 将old_value标记为灰色放入SATB队列satb_queue.add(old_value);}// 执行实际的写入obj.fieldnew_value;}图解过程时刻T0并发标记开始 GC Roots → A → B ↘ C 时刻T1应用线程修改引用 A.next null ← 触发了写屏障 写屏障将 old_value(B) 加入SATB队列 时刻T2并发标记继续 GC线程处理SATB队列 → 发现B → 从B开始继续扫描 结果B被标记为存活即使A不再引用它但C最终会引用它四、最终标记具体做什么输入并发标记阶段处理的SATB队列可能还有残留并发标记阶段未处理完的灰色对象输出所有存活对象都被确认为黑色所有死亡对象保持白色执行过程代码演示// 最终标记STW阶段的伪代码voidfinal_mark(){// 1. 处理所有SATB队列中残留的引用while(!satb_queue.isEmpty()){Objectobjsatb_queue.poll();if(is_white(obj)){mark_gray(obj);// 标记为灰色加入工作队列}}// 2. 继续三色标记处理完所有灰色对象while(!work_queue.isEmpty()){Objectgraywork_queue.poll();scan_all_references(gray);// 扫描其所有引用mark_black(gray);// 标记为黑色}// 3. 此时所有存活对象都是黑色白色对象就是垃圾}五、完整例子从开始到结束场景代码classNode{Nodenext;}publicclassTest{publicstaticvoidmain(String[]args){NodeanewNode();NodebnewNode();NodecnewNode();a.nextb;// A → B// GC并发标记开始时快照记录A→B// 并发标记期间应用线程执行a.nextnull;// 切断A→Bc.nextb;// C→B建立新引用// 如果只用三色标记B会变白 → 误回收// 但SATB写屏障保住了B}}执行时序表步骤动作算法结果1并发标记开始三色标记 SATB快照记录A→B关系2应用线程执行a.nextnullSATB写屏障B被加入SATB队列3应用线程执行c.nextb无屏障新引用直接赋值C引用B4并发标记继续三色标记从GC Roots遍历到C但C是白色5最终标记开始STW处理SATB队列取出B标记为灰色6最终标记继续三色标记收尾B→黑色C→黑色7标记完成-所有对象存活无误回收六、SATB vs CMS增量更新关键对比维度G1 (SATB)CMS (增量更新)记录时机写入前记录旧引用写入后记录新引用记录内容old_value被覆盖的引用new_value新建立的引用标记准确性保守可能保留浮动垃圾精确但容易漏标漏标风险低SATB快照保证高需要额外处理复杂度相对简单复杂需要重新标记堆七、最终答案总结并发标记Concurrent Marking ├── 算法三色标记Tri-color Marking ├── 支撑SATBSnapshot-At-The-Beginning 写屏障 ├── 作用遍历对象图标记大部分存活对象 └── 特点与应用线程并发执行 最终标记Final Marking ├── 算法SATB队列处理 三色标记收尾 ├── 支撑SATB机制 ├── 作用处理并发期间引用变化带来的遗留问题 └── 特点STW停顿但非常短暂两者关系并发标记是主体工作最终标记是收尾修正共同保证标记结果的准确性为后续的垃圾优先回收提供正确数据。如果想进一步了解G1的写屏障在JVM源码层面如何实现或者浮动垃圾如何影响GC效率可以继续问我。