连载中 6/20

垃圾标记:可达性分析与四种引用的分工

2026-09-13 · 160 阅读 · 0 评论 · 0 赞

引用计数的死穴

判断对象死活,最直觉的方案是引用计数:被引用一次计数加一,归零即死。Python 和 COM 都在用,但 JVM 不用——除了每次赋值都要维护计数器的开销,更致命的是循环引用:A 引用 B、B 引用 A,两个对象再无外人引用,计数却永远是 1,永远回收不掉。

Node a = new Node();   // a.count = 1
Node b = new Node();   // b.count = 1
a.next = b;  b.next = a;   // 互相引用,count 各自变成 2
a = null;    b = null;    // 外部引用断开,count 降回 1
// 引用计数视角:两个对象都还活着
// 实际情况:谁也访问不到它们了——内存泄漏

可达性分析

JVM 的解法是换思路:不数引用,而是选定一批GC Roots,从它们出发沿引用链往下摸,摸得到的算活人,摸不到的判死刑。GC Roots 的花名册要记牢:栈帧局部变量表(正在执行的方法里握着的)、静态变量常量引用JNI 引用、活跃线程本身。排查内存泄漏时 MAT 里"path to GC Roots"就是在逆着这条链找"谁把对象拴住了"。

不可达也不等于立刻回收:对象还有一次自我拯救的机会——如果重写了 finalize() 且从未执行过,会被丢进 F-Queue 低优先级执行,执行时把自己重新挂到引用链上就复活了(机会只有一次)。这机制设计得鸡肋,JDK 9 起已标记废弃,知道即可,别指望它。

四种引用各守一岗

Object strong = new Object();                       // 强:GC 饿死也不收
SoftReference<Object> soft = new SoftReference<>(new Object());
// 软:内存不足才收——适合图片缓存这类"有最好,没有也能活"
WeakReference<Object> weak = new WeakReference<>(new Object());
// 弱:下次 GC 必收——ThreadLocalMap 的 key 就是它
PhantomReference<Object> phantom = new PhantomReference<>(obj, queue);
// 虚:形同虚设,只为回收时收到通知——DirectByteBuffer 靠它清理堆外内存

四档引用本质是给 GC 递了四张不同态度的纸条:强引用说"我在它就在",软引用说"实在缺内存再拿走",弱引用说"下一次 GC 随便处置",虚引用说"收走时吱一声就行"。日常代码 99% 用强引用,剩下 1% 的灵活性全靠后三种。

ThreadLocal 泄漏的真相

ThreadLocalMap 的 entry 里,key 是弱引用,value 是强引用。key 被 GC 后变 null,但 value 还被 entry 拴着——只要线程不死(线程池的线程恰恰长命百岁),这条 stale entry 就一直占着内存。JVM 自己会在 get/set 时顺手清理部分 stale entry,但清理时机不可靠,唯一的正解是 try/finally 里手动 remove()——用完即拆,别指望房子自己打扫。

根节点枚举为什么必须停顿

摸清 GC Roots 要求"引用关系静止不动",所以根节点枚举这一步必须 Stop The World——好在 HotSpot 用 OopMap 提前记录了哪里是引用,这一步快到微秒级;再配合安全点机制,只在"大家都停下来不添乱"的时刻做枚举。停顿能压多短,取决于后续标记、回收阶段有多少能并发——这正是下一篇各路回收算法与收集器表演的舞台。

503

10 年全栈工程师 · 503咖啡馆主理人

#可达性分析#GC Roots#四种引用#ThreadLocal泄漏#finalize

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 1 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 12 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 7 阅读 · 0 评论 · 0 赞