连载中 6/20

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

2026-09-13 · 161 阅读 · 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)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10101 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9873 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9294 阅读 · 21 评论 · 287 赞