一、背景
在JVM的垃圾回收机制中,准确识别哪些对象是垃圾(即不再被引用的对象)是核心任务之一,通常采用根可达性分析算法。
传统的垃圾回收算法如标记-清除(Mark-Sweep)、标记-整理(Mark-Compact)等在标记阶段通常会产生STW(Stop The World)方式,这会导致应用程序出现明显的停顿。为了减少这种停顿时间,JVM引入了三色标记算法,它是现代垃圾回收器(如CMS、G1、ZGC等)的核心算法之一。
二、三色的含义
三色标记算法将对象分为三种颜色,每种颜色代表不同的状态
1、白色对象
- 定义:尚未被垃圾回收器访问到的对象
- 初始状态:在标记开始阶段,所有对象均为白色,表示"未被发现"或"待处理"状态
- 最终状态:标记结束后,仍然是白色的对象被视为垃圾对象,会在后续的清除阶段被回收
2、灰色对象
- 定义:已经被垃圾回收器访问到,但该对象引用的其他对象还没有被完全扫描,表示"已发现但未处理完"状态
- 中间状态:表示该对象正在被处理中,其部份引用已经被扫描,但还有一些引用未被扫描
3、黑色对象
- 定义:已经被垃圾回收器访问到,并且该对象引用的所有其他对象也都已经被扫描过,表示"已处理完成"状态
- 终结状态:黑色对象不会再被重新扫描,垃圾回收器认为其引用的所有对象都已经被标记
三、三色标记算法流程
1、初始阶段
- 所有对象均被标记为白色
2、标记阶段
- 根对象标记:垃圾回收器从GCRoots根对象(如 栈中的引用、静态变量引用等)开始扫描,将根对象标记为灰色
- 灰色对象处理:依次处理每个灰色对象,将其引用的所有白色对象标记为灰色,并将该灰色对象自身标记为黑色
- 循环处理:重复上述步骤,直到所有灰色对象都变为黑色对象为止
3、完成阶段
- 所有对象的颜色均为黑色或白色,白色对象即为垃圾对象,会在后续的清除阶段被回收
四、漏标问题
1、漏标问题的原因
在并发标记过程中,由于用户线程和垃圾回收线程同时运行,可能会出现以下两种情况,导致对象被错误的标记为垃圾:
- 条件一:一个白色对象(未扫描的对象)被黑色对象(已扫描的对象)引用
- 条件二:从灰色对象到该白色对象(未扫描的对象)的直接或间接引用被破坏
当这两个条件同时满足时,由于黑色对象不会被再次扫描,而且灰色对象也无法引用这个白色对象,所以会导致白色对象被错误的标记为垃圾,这种现象称为"对象消失问题",也叫漏标。
2、漏标问题的解决方案
(1)增量更新
- 原理:当黑色对象插入新的指向白色对象的引用时,将插入操作记录下来(通过写屏障)。在重新标记阶段,重新处理这些记录,确保白色对象被正确标记。
- 应用场景:CMS垃圾回收器采用增量更新。
(2)原始快照
- 原理:当灰色对象要删除指向白色对象的引用时,将这个要删除的引用记录下来。在重新标记阶段,将这些记录中的白色对象标记为灰色,确保他们不会被错误回收。
- 应用场景:G1和ZGC采用原始快照。