ThreadLocal 天天在用,内存泄漏真在我们线上发生了
一直觉得 ThreadLocal 内存泄漏是面试题,直到上季度 GC 日志里老年代出现规律的锯齿,才知道它离生产事故并不远。
先说结构:每个 Thread 内部有一个 ThreadLocalMap,Entry 的 key 是弱引用指向 ThreadLocal 对象,value 是强引用。当外部不再持有 ThreadLocal 时,key 会被 GC 回收变成 null,但 value 还被 Entry 强引用着,只要线程活着就释放不掉。业务代码里通常是 static final 的 ThreadLocal,key 泄漏风险小,真正的雷是忘了 remove:值本身一直挂在长生命周期线程上。
我们的事故就是登录态拦截器往 ThreadLocal 塞了用户上下文,链路上有个分支提前 return,绕过了 finally 里的清理。Tomcat 线程是复用的,几十个线程各自挂着一个旧上下文,一套权限菜单对象 300KB,dump 里肉眼可见 ThreadLocalMap 的 value 全是 PermissionContext。
修复模板很固定:
try {
UserContext.set(currentUser);
return doBusiness(request);
} finally {
// 出口必须清理,无论哪个分支返回
UserContext.remove();
}
另外两点:线程池场景用 ThreadLocal 传递轨迹 ID 会丢,换阿里的 TransmittableThreadLocal,用 TtlExecutors 包装线程池;怀疑泄漏时用 jcmd PID GC.class_histogram 或直接 MAT 看 ThreadLocalMap 的持有链。规范比技巧有用——我们后来把入口设值、出口清理做成了 AOP 切面,业务代码不再手动碰 ThreadLocal。
- 点赞
- 收藏
- 关注作者
评论(0)