ThreadLocal 内存泄漏:`Entry` 的 key 都用弱引用了,为什么还会泄漏

发布时间:2026/8/6 8:51:18

ThreadLocal 内存泄漏:`Entry` 的 key 都用弱引用了,为什么还会泄漏
前言ThreadLocal是个好东西给每个线程一份独立的变量副本天然线程隔离常用来存用户上下文、事务、SimpleDateFormat这类每线程一份的东西。但它有个著名的坑用不好会内存泄漏。更让人困惑的是很多人知道ThreadLocalMap的Entry用了弱引用来防泄漏于是产生一个疑问——既然都用弱引用了为什么还会泄漏这正是这篇文章要讲清楚的。弱引用确实解决了一半问题但另一半value它管不到而线程池又把这个隐患放大成了实实在在的线上事故。环境说明本文基于 JDK 8。涉及的引用类型、GC 概念属于 Java 内存管理基础。一、先复现一个会让内存慢慢涨的接口设想一个 Web 接口用ThreadLocal缓存一个比较大的上下文对象publicclassUserContextHolder{privatestaticfinalThreadLocalUserContextCONTEXTnewThreadLocal();publicstaticvoidset(UserContextctx){CONTEXT.set(ctx);}publicstaticUserContextget(){returnCONTEXT.get();}// 注意这里没有提供 remove()也没人调用}// 拦截器里每个请求进来就 set 一份publicclassContextInterceptorimplementsHandlerInterceptor{publicbooleanpreHandle(HttpServletRequestreq,...){UserContextctxbuildContext(req);// 假设这个对象不小UserContextHolder.set(ctx);returntrue;}// 请求结束后没有 remove}这段代码功能上完全正常测试也没问题。但把它放到生产环境用 Tomcat 默认的线程池跑一段时间后你会观察到堆内存缓慢但持续地增长老年代越堆越满最终频繁 Full GC 甚至 OOM。问题在于ThreadLocal用完了没有remove()而线程池里的线程一直活着不销毁那些UserContext对象就一直被挂在线程上GC 回收不掉。要理解为什么得先看ThreadLocal的存储结构。二、根因/底层弱引用只保护了 keyvalue 没人管2.1 数据到底存在哪不是存在 ThreadLocal 里第一个反直觉的点ThreadLocal.set(value)的值并不存在ThreadLocal对象里而是存在当前线程身上。每个Thread对象内部有一个字段threadLocals类型是ThreadLocal.ThreadLocalMap。你调用threadLocal.set(value)时实际是以这个ThreadLocal实例为 key、你的value为 value存进了当前线程的那个ThreadLocalMap。Thread线程对象 └─ threadLocals: ThreadLocalMap └─ Entry[] 每个 Entry 是一个 key-value 对 key ThreadLocal 实例弱引用 value 你 set 进去的值强引用这个设计的好处是天然隔离不同线程有各自的ThreadLocalMap互不干扰。2.2 关键Entry的 key 是弱引用value 是强引用ThreadLocalMap里的Entry定义是这样的简化staticclassEntryextendsWeakReferenceThreadLocal?{Objectvalue;Entry(ThreadLocal?k,Objectv){super(k);// keyThreadLocal作为弱引用valuev;// value 是强引用普通字段}}注意这个不对称的设计keyThreadLocal实例是弱引用Entry extends WeakReferenceThreadLocalkey 被弱引用持有。value你 set 的值是强引用value就是个普通字段被Entry强引用着。为什么 key 要用弱引用就是为了防泄漏当外部不再引用这个ThreadLocal时比如ThreadLocal变量被置空或超出作用域弱引用不阻止 GCkey 就能被回收掉Entry的 key 变成null。设计者的本意是好的但问题恰恰出在这个一半弱、一半强上。2.3 泄漏是怎么发生的key 没了value 还在设想这样一条引用链当外部对ThreadLocal的强引用消失后key 是弱引用 →GC 时被回收Entry的 key 变成null但 value 是强引用它的引用链是Thread→ThreadLocalMap→Entry→value。只要线程还活着这条强引用链就一直在value就永远回收不掉。结果就是ThreadLocalMap里出现一堆key 为null、value 还占着内存的僵尸 Entry——这就是内存泄漏。2.4 为什么线程池让问题致命如果是普通线程用完就结束Thread对象被回收它的ThreadLocalMap连同里面所有Entry、value 一起被回收泄漏也就自愈了——所以短生命周期的线程问题不明显。但线程池里的线程是复用的、长期存活的。一个线程处理完请求 A不会销毁而是回到池里等着处理请求 B、C、D……它的ThreadLocalMap一直存在。于是每个请求set一个UserContext用完不remove线程不死ThreadLocalMap不释放僵尸 Entry或旧 value越积越多内存持续增长最终 OOM。“ThreadLocal 线程池 忘记 remove” 是内存泄漏的黄金三角。这也是为什么这个坑在 Web 应用Tomcat 线程池里特别常见。JDK 其实做了点补救ThreadLocalMap在set/get/remove时会顺带清理一些 key 为null的僵尸 Entry探测式清理。但这个清理是碰运气的、不彻底的绝不能依赖它。根治办法只有一个手动remove。三、正解用完一定remove()最好放在finally里根治方案非常简单每次用完ThreadLocal显式调用remove()。remove()会把当前线程ThreadLocalMap里对应的整个Entrykey 和 value都删掉斩断强引用链。关键是要保证remove()一定被执行所以放在finally里publicbooleanpreHandle(HttpServletRequestreq,...){UserContextHolder.set(buildContext(req));returntrue;}// 在请求结束的回调里 removeSpring 的 afterCompletionpublicvoidafterCompletion(HttpServletRequestreq,...){UserContextHolder.remove();// ✓ 请求结束清理}或者在业务代码里用标准的try-finally包裹try{UserContextHolder.set(ctx);doBusiness();}finally{UserContextHolder.remove();// ✓ 无论是否异常都清理}finally里做清理正是它的正确用法——可参考上一篇《try-finally 里的 return》。几个补充实践拦截器/过滤器场景在afterCompletion或finally里统一remove别依赖 JDK 的探测式清理。把ThreadLocal声明为static final让它跟随类存在避免它被意外回收其实反而是key 不该被过早回收也便于统一管理。注意这和防泄漏不矛盾——防泄漏靠的是remove不是让 key 被回收。父子线程传递用InheritableThreadLocal但线程池下要谨慎线程复用会导致继承的值错乱阿里的TransmittableThreadLocalTTL是更完善的方案。四、常见误区与面试高频问答QEntry的 key 用了弱引用不就是为了防泄漏吗为什么还漏弱引用只解决了keyThreadLocal 实例的回收让没人引用的ThreadLocal能被 GC。但value 是强引用它通过Thread → ThreadLocalMap → Entry → value这条链被线程强引用着只要线程活着就回收不掉。弱引用防了 key防不了 value——这才是泄漏的根源。Q那 key 为什么不干脆也用强引用或者 value 也用弱引用key 用强引用会更糟ThreadLocalMap会强引用ThreadLocal导致ThreadLocal实例本身也回收不掉泄漏更严重。value 用弱引用又不行value 通常没有其他强引用一 GC 就没了ThreadLocal就存不住值了。所以现在这个key 弱、value 强是权衡后的设计代价就是需要你手动remove。QJDK 不是会自动清理 null key 的 Entry 吗会但不可靠。set/get/remove时会触发探测式/启发式清理顺路清掉一些 key 为null的 Entry。但它只清理碰到的部分槽位不保证全清更不会主动触发。如果后续不再调用这个ThreadLocal的方法僵尸 Entry 就一直留着。不能依赖它必须手动remove。Q为什么普通线程没事线程池才严重普通线程执行完就销毁Thread及其ThreadLocalMap整个被回收泄漏自动消失。线程池的线程长期复用、不销毁ThreadLocalMap一直存在不remove的话 value 越积越多泄漏就暴露了。Qremove()和set(null)一样吗不一样。set(null)只是把 value 设为nullEntry本身key 和这个 null value还留在 map 里是半清理。remove()会把整个Entry从 map 中删除才是彻底清理。要remove()。总结“ThreadLocal 的 key 是弱引用为什么还泄漏”答案在那个不对称的设计里ThreadLocal的值存在线程的ThreadLocalMap里Entry的keyThreadLocal是弱引用、value你的值是强引用。弱引用让没人用的 key 能被 GC 回收key 变null但value 仍被Thread → ThreadLocalMap → Entry → value强引用链拴着只要线程活着就回收不掉形成key 为 null、value 常驻的僵尸 Entry。线程池里线程长期复用、不销毁把这个隐患放大成持续的内存泄漏直至 OOM。根治办法只有一个用完remove()并放在finally/afterCompletion里确保执行。别指望 JDK 的探测式清理。一句话记忆弱引用只保护 keyvalue 是强引用、被活着的线程拴着回收不掉ThreadLocal 线程池 忘记 remove 内存泄漏用完必须remove()。

相关新闻

自动驾驶仿真利器Carla:从游戏引擎到算法测试沙盒

自动驾驶仿真利器Carla:从游戏引擎到算法测试沙盒

2026/8/6 8:41:17

1. 从游戏引擎到自动驾驶仿真:Carla的诞生与定位 如果你正在研究自动驾驶,或者对机器人仿真感兴趣,那么“Carla”这个名字你大概率不会陌生。它不是一个简单的游戏,也不是一个纯粹的物理引擎,而是一个专门为自动驾驶研…

正定矩阵四大核心性质:从定义到Cholesky分解的工程实践

正定矩阵四大核心性质:从定义到Cholesky分解的工程实践

2026/8/6 8:41:17

1. 项目概述:为什么我们要深挖正定矩阵的性质? 在工程计算、机器学习优化和物理系统分析里,我们经常会遇到一类特殊的矩阵,它们被称为“正定矩阵”。我第一次系统性地理解这个概念,是在研究一个结构力学仿真问题的时候…

Unity InputSystem跨平台输入管理:告别旧InputManager,实现一套代码支持PC、手机与手柄

Unity InputSystem跨平台输入管理:告别旧InputManager,实现一套代码支持PC、手机与手柄

2026/8/6 8:41:17

1. 项目概述:为什么我们要告别InputManager? 如果你还在用Unity自带的旧InputManager,吭哧吭哧地为PC键盘、手机触屏、Xbox手柄、PS手柄分别写几套输入处理代码,那今天这篇内容就是为你准备的。我经历过那个阶段,一个简…

3个关键问题揭示:为什么传统通达信数据处理效率低下?

3个关键问题揭示:为什么传统通达信数据处理效率低下?

2026/8/6 11:51:31

3个关键问题揭示:为什么传统通达信数据处理效率低下? 【免费下载链接】mootdx 通达信数据读取的一个简便使用封装 项目地址: https://gitcode.com/GitHub_Trending/mo/mootdx 在量化投资和金融分析领域,通达信数据是许多从业者不可或缺…

大模型会写代码,你为什么反而更难找工作?

大模型会写代码,你为什么反而更难找工作?

2026/8/6 11:51:31

这篇不先堆名词。我们把《别急着重做计算机专业就业,先看岗位到底在筛什么》拆成几级台阶,看完至少知道下一步该学什么、该练什么。摘要我上周帮一个学弟看简历,他项目里写了三个 LangChain Agent,还能跑通 RAG 问答。我问了他一个…

游戏聊天与社交系统怎么测:消息状态、敏感内容、举报、屏蔽与未成年人保护

游戏聊天与社交系统怎么测:消息状态、敏感内容、举报、屏蔽与未成年人保护

2026/8/6 11:51:31

游戏聊天与社交系统怎么测:消息状态、敏感内容、举报、屏蔽与未成年人保护摘要:聊天专项既有实时消息的一致性问题,也有内容安全和社区治理风险。本篇覆盖频道、顺序、重发、敏感内容、举报证据、屏蔽关系和处置闭环,不提供绕过审…

原神帧率解锁终极指南:三步轻松突破60帧限制,畅享高刷新率游戏体验

原神帧率解锁终极指南:三步轻松突破60帧限制,畅享高刷新率游戏体验

2026/8/6 11:51:31

原神帧率解锁终极指南:三步轻松突破60帧限制,畅享高刷新率游戏体验 【免费下载链接】genshin-fps-unlock unlocks the 60 fps cap 项目地址: https://gitcode.com/gh_mirrors/ge/genshin-fps-unlock 厌倦了《原神》游戏中60帧的限制吗&#xff1f…

游戏反作弊怎么测:信任边界、重放、篡改、误封与处置闭环

游戏反作弊怎么测:信任边界、重放、篡改、误封与处置闭环

2026/8/6 11:51:31

游戏反作弊怎么测:信任边界、重放、篡改、误封与处置闭环摘要:反作弊测试不是寻找“外挂能不能开”。高分回答要从服务端信任边界、关键业务不变量、检测证据、误报控制和处置恢复建立完整质量模型,并在受控环境内验证。标签:游戏…

2026年8月,AI API 价格战进入“终局“:当智能变成白菜价,竞争逻辑彻底变了

2026年8月,AI API 价格战进入“终局“:当智能变成白菜价,竞争逻辑彻底变了

2026/8/6 11:41:31

> 7月31日,注定是 AI API 市场值得记住的一天:OpenAI 宣布 GPT-5.6 Luna 降价 80%,DeepSeek V4-Flash 正式版上线且 Agent 能力暴涨 6 倍,MiniMax 开源视频模型 H3 登顶全球榜单,字节跳动发布 Seedance 2.5……一天…

ncmdumpGUI:一键解锁网易云音乐ncm文件的终极解决方案

ncmdumpGUI:一键解锁网易云音乐ncm文件的终极解决方案

2026/8/4 15:23:37

ncmdumpGUI:一键解锁网易云音乐ncm文件的终极解决方案 【免费下载链接】ncmdumpGUI C#版本网易云音乐ncm文件格式转换,Windows图形界面版本 项目地址: https://gitcode.com/gh_mirrors/nc/ncmdumpGUI 你是否曾经从网易云音乐下载了心爱的歌曲&am…

分布式配置中心选型实战:Nacos与Consul在创业场景下的对比

分布式配置中心选型实战:Nacos与Consul在创业场景下的对比

2026/8/5 6:02:27

分布式配置中心选型实战:Nacos与Consul在创业场景下的对比工程导读:本文深入讨论 分布式配置中心选型实战:Nacos与Consul在创业场景下的对比 在生产工程实践中的核心落地方案。基于 分布式架构与微服务设计 视角,剖析实际痛点、架…

MoneyPrinterPlus实战指南:AI视频批量生成与自动化发布完整解决方案

MoneyPrinterPlus实战指南:AI视频批量生成与自动化发布完整解决方案

2026/8/5 8:19:55

MoneyPrinterPlus实战指南:AI视频批量生成与自动化发布完整解决方案 【免费下载链接】MoneyPrinterPlus AI一键批量生成各类短视频,自动批量混剪短视频,自动把视频发布到抖音,快手,小红书,视频号上,赚钱从来没有这么容易过! 支持本地语音模型chatTTS,fasterwhisper,…

Unity相机抖动插件Camera-Shake集成与应用实战指南

Unity相机抖动插件Camera-Shake集成与应用实战指南

2026/8/6 0:00:51

1. 项目概述与核心价值最近在做一个动作游戏,需要给主角的重击和爆炸场景加点料,让打击感更足。我第一时间就想到了给相机加个抖动效果,毕竟这是提升玩家沉浸感最简单直接的手段之一。自己手写一个也不是不行,但时间成本高&#x…

Cocos Creator 3.7微信小游戏开发:从架构设计到提审上线的全流程实战指南

Cocos Creator 3.7微信小游戏开发:从架构设计到提审上线的全流程实战指南

2026/8/6 0:00:51

1. 项目概述:为什么需要一份3.7版本的专属适配指南?如果你是一位使用Cocos Creator开发微信小游戏的开发者,并且项目正运行在3.7版本上,那么你很可能已经感受到了那份“甜蜜的烦恼”。一方面,Cocos Creator 3.7是一个功…

AI编程实战:从Prompt工程到工具链集成,打造高效开发工作流

AI编程实战:从Prompt工程到工具链集成,打造高效开发工作流

2026/8/6 0:00:51

1. 项目概述:一次开源AI编程课程的深度重构 最近,我把自己的开源AI编程课程《Claude Code》做了一次从里到外的大更新。如果你对利用Claude、Codex这类大模型来辅助编程感兴趣,或者正在寻找一个能跟上最新AI编码工具迭代节奏的学习路径&#…

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

2026/8/6 5:43:30

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…

导师推荐!2026最新AI论文工具测评与实用推荐

导师推荐!2026最新AI论文工具测评与实用推荐

2026/8/4 14:25:14

2026年真正好用的AI论文工具,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

告别游戏崩溃:XCOM 2模组管理器的智能革命

告别游戏崩溃:XCOM 2模组管理器的智能革命

2026/8/4 15:11:03

告别游戏崩溃:XCOM 2模组管理器的智能革命 【免费下载链接】xcom2-launcher The Alternative Mod Launcher (AML) is a replacement for the default game launchers from XCOM 2 and XCOM Chimera Squad. 项目地址: https://gitcode.com/gh_mirrors/xc/xcom2-lau…