一、核心原理:找风筝线
比喻:对象就像风筝,只要还有线(引用链)连着地面(GC Roots),就不是垃圾
1. 可达性分析算法(主流方案)
-
步骤:
1️⃣ 扫描所有 GC Roots(地面上的固定桩)
2️⃣ 沿着引用链标记所有可达对象(连着线的风筝)
3️⃣ 未被标记的对象判定为可回收(断线的风筝) -
源码佐证:HotSpot 的
G1RootProcessor类处理根节点枚举// hotspot/share/gc/g1/g1RootProcessor.cpp void G1RootProcessor::process_strong_roots(...) { // 遍历所有GC Roots类型 _process_strong_tasks->do_serial_threads(); // Java线程栈 _process_strong_tasks->do_universe(); // 系统类加载器 _process_strong_tasks->do_jni_handles(); // JNI全局引用 }
2. GC Roots 具体包括
| 类型 | 示例 | 底层存储位置 |
|---|---|---|
| 虚拟机栈中的引用 | 方法局部变量、参数 | 各线程栈帧的局部变量表 |
| 本地方法栈中的引用 | JNI 本地方法引用的对象 | Native 方法栈 |
| 方法区静态引用 | static 修饰的类变量 | 方法区的静态变量区 |
| 方法区常量引用 | final 修饰的常量 | 方法区的运行时常量池 |
| 同步锁持有对象 | synchronized 锁对象 | 对象头的Mark Word关联 |
二、对象标记技术:给风筝贴标签
1. 三色标记法(并发标记的关键)
-
白色:未被扫描(可能垃圾)
-
灰色:已扫描但子节点未扫描
-
黑色:已扫描且子节点也完成扫描
-
HotSpot实现:使用位图(Bitmap)标记状态
// hotspot/share/gc/shared/markBitMap.hpp class MarkBitMap { bool mark(HeapWord* addr) { // 通过位运算设置标记位 *bit_index(addr) |= mask(addr); } };
2. 漏标问题与解决方案
-
问题场景:并发标记时,用户线程修改引用导致漏标
原始状态:A(黑) → B(白) 用户操作:C(灰) → B(白),并删除 A → B 结果:B被漏标 -
解决方案:
-
增量更新(CMS使用) :记录黑色对象新增的引用(把黑变灰)
-
原始快照(G1使用) :记录灰色对象删除的引用(SATB队列)
// G1的SATB处理(hotspot/share/gc/g1/g1SATBMarkQueue.hpp) void G1SATBMarkQueue::enqueue(oop obj) { if (!_buffer->is_full()) { _buffer->push(obj); // 记录被删除的引用 } } -
三、四种引用类型:风筝线的材质
| 引用类型 | 回收时机 | 源码实现类 | 使用场景 |
|---|---|---|---|
| 强引用 | 永不回收(除非线断) | 默认 | 普通对象 |
| 软引用 | 内存不足时回收 | SoftReference | 缓存 |
| 弱引用 | 下次GC必回收 | WeakReference | 临时缓存(如WeakHashMap) |
| 虚引用 | 随时回收,仅收尸通知 | PhantomReference | 堆外内存监控 |
示例代码:
// 软引用示例(内存不足时才回收)
SoftReference<byte[]> cache = new SoftReference<>(new byte[1024*1024]);
if (cache.get() != null) {
// 缓存还有效
}
底层处理:Reference 类的状态机(pending 列表)
// hotspot/share/classfile/javaClasses.cpp
void java_lang_ref_Reference::notify_reference_reachability(...) {
// 将可达性变化的引用加入队列
enqueue_reference(ref);
}
四、finalize() 方法:临终抢救机会
特殊规则:
- 对象第一次被标记为不可达时,会进入
F-Queue队列 - Finalizer 线程异步执行
finalize()方法 - 若在
finalize()中重新建立引用,对象复活
源码流程:
// hotspot/share/gc/shared/referenceProcessor.cpp
void ReferenceProcessor::process_discovered_references(...) {
// 处理FinalReference
if (policy == ReferencePolicy::ALWAYS) {
enqueue_final_references(...);
}
}
致命问题:
finalize()执行时间不可控,可能阻塞 Finalizer 线程- 复活后再次死亡时不会再调用
finalize()
五、对象真正死亡的条件
必须同时满足:
- 不可达:从GC Roots出发无法访问到该对象
- 未复活:
finalize()未成功重新建立引用 - 无关联:该对象的类已被卸载(非必须条件)
六、总结口诀
「判断生死看引用,可达分析找根源
三色标记防漏标,增量快照解疑难
软弱虚引用各不同,finalize可复活
对象头里藏标记,位图操作见真章!」
附:HotSpot 参数验证
-XX:+PrintGCDetails:查看GC日志中的对象回收情况-XX:+TraceReferenceGC:追踪引用处理过程