ThreadLocal 不是银弹:一次内存泄漏把老年代打满的复盘
引子为什么人人都用 ThreadLocalWeb 开发里ThreadLocal几乎无处不在Spring 的RequestContextHolder、MyBatis 的PageHelper、日志链路追踪的 traceId底层都靠它把只属于当前请求的数据绑在线程上免去一层层传参。它本身不难一行set、一行get但一旦用在线程池环境坑就来了。我们线上一次老年代被打满、Full GC 每 5 秒一次的事故根因就是ThreadLocal没清理。问题线程池 ThreadLocal 泄漏温床关键事实Tomcat、各种 RPC 框架处理请求用的都是线程池线程是复用的不会被销毁。而ThreadLocal的值存在线程自己的ThreadLocalMap里Thread不死这个 Map 就不释放。如果你在请求处理里set了一个大对象处理完没remove那么这个对象会一直挂在线程上被下一个请求、下下个请求反复复用和累积直到老年代撑爆。我们当时的代码长这样private static final ThreadLocalUserContext ctx new ThreadLocal(); public void handle(Request req) { ctx.set(loadUserContext(req)); // 1. set 了大对象 try { bizService.process(); // 2. 业务逻辑内部各处 get(ctx) } finally { // 3. 这里忘了 remove —— 事故起点 } }逐行看问题第 2 行set把UserContext里面塞了我们系统的用户权限树平均 200KB挂到当前线程。第 6 行finally里我们只写了日志没写remove。在线程池下这个线程处理完返回池子200KB 的UserContext跟着线程一起存活。线程池 200 个线程每个都残留一份就是 40MB而且因为对象一直被强引用永远到不了回收Full GC 也清不掉——这正是我们老年代暴涨的直接原因。源码/原理为什么泄漏以及为什么 JDK 已经尽力了ThreadLocal的存储不是存在ThreadLocal对象里而是存在Thread.threadLocals这个ThreadLocalMap里key 是ThreadLocal本身弱引用value 是你 set 的对象强引用。// ThreadLocalMap 的 Entry 定义简化 static class Entry extends WeakReferenceThreadLocal? { Object value; // 1. value 是强引用 Entry(ThreadLocal? k, Object v) { super(k); // 2. key 是弱引用指向 ThreadLocal value v; } }逐行解释这个设计的微妙之处第 2 行 key 用弱引用意思是当你代码里的ThreadLocal变量比如上面的ctx静态字段因为类卸载或置 null 而失去强引用时下一次 GC 会把 key 清掉。这是 JDK 为了减少泄漏做的努力。但第 1 行的value是强引用。key 被 GC 清成 null 后value仍然挂在Entry上而Entry挂在ThreadLocalMap上ThreadLocalMap挂在Thread上。Thread不死这条强引用链就断不了——value 泄漏了。所以弱引用只解决了key 那一半的问题value 这边的泄漏 JDK 帮不了你只能靠你自己remove。set/get时 JDK 会顺手做一次过期 entry 清理expungeStaleEntry但这只是擦边球——它只在你再次访问同一个 ThreadLocal 且恰好遇到 stale slot时才清理不能替代显式remove。实战事故当晚我们怎么定位和止血定位过程其实很简单粗暴用jmap -histo:live pid抓堆排第一的是一个UserContext类实例数 200和线程池大小吻合。再jstack一看这些UserContext的引用链都指向Thread.threadLocals。根因锁定没remove。止血用三步public void handle(Request req) { ctx.set(loadUserContext(req)); try { bizService.process(); } catch (Exception e) { log.error(process failed, e); throw e; } finally { ctx.remove(); // 1. 唯一的正解用完即清 } }第 8 行ctx.remove()会在ThreadLocalMap里删除这条 entry断开 value 的强引用下次 GC 就能回收。这是我们当晚加的补丁。但这里还有个细节如果bizService.process()抛异常且外层没捕获会不会跳过finally不会——finally无论如何都会执行所以把remove放finally是对的。真正的风险是有人把remove放在try的正常分支里而忘了异常分支那样异常时还是漏。第二步我们顺手把静态ThreadLocal改成每次请求用完后必然 remove并对所有ThreadLocal使用点做了一次全局 grep发现还有两处PageHelper风格的手动分页没清理一并修了。第三步加监控老年代使用率超过 80% 就告警避免下次再等到 Full GC 风暴才发现。对比几种请求级上下文方案的取舍方案是否泄漏风险适用静态 ThreadLocal 手动 remove忘了 remove 就泄漏简单请求上下文InheritableThreadLocal线程池下值串号父子线程传参慎用TransmittableThreadLocal (阿里)需引入依赖解决线程池传递线程池 上下文传递方法参数显式传递无泄漏但代码啰嗦调用链短的场景我的判断纯请求上下文、且你能保证finally里remove用原生ThreadLocal足够。但只要你的任务会被线程池跨线程传递比如把上下文塞进异步任务丢给别的线程池跑原生ThreadLocal直接失效——值传不过去这时候得上阿里的TransmittableThreadLocal。总结与我的取舍ThreadLocal的价值在于隐式传参但代价是隐式持有。它在线程池时代最大的敌人就是遗忘的remove。我现在给自己定的铁律任何ThreadLocal.set必须配对try/finally { remove() }宁可多写两行也不留泄漏口。我不建议在业务代码里大量自建ThreadLocal存大对象——大对象一旦泄漏危害远超传参那点便利。能显式传参的尽量显式传非用不可的把存的东西压到最小并确保remove。复盘数据那次事故的具体数字把当晚的监控数字摊开更有说服力出问题的服务是 4 核 8G 容器Tomcat 默认线程池 200 线程每个请求的UserContext平均 200KB里面是用户权限树 菜单树。泄漏后老年代在约 20 分钟内从 1.2GB 涨到 7.5GBFull GC 从几乎不发生变成每 5 秒一次单次停顿 600ms~1.2s接口 P99 从 80ms 飙到 3s 以上。加完remove并重启后老年代稳定在 1.5GB 上下Full GC 归零P99 回到 90ms。事后我们算了一笔账200 线程 × 200KB 40MB 常驻看着不多但它进的是老年代且永不被回收叠加每次请求新建的临时对象老年代上涨速度远超回收速度。这也是为什么jmap -histo:live一眼就能看出——UserContext实例数正好等于线程池大小是个很明显的指纹。补充一个版本相关的点ThreadLocal从 Java 1.2 就有InheritableThreadLocal同期但当任务要跨线程池传递上下文时原生方案会失效我们后来在异步链路里换成了阿里的TransmittableThreadLocal当时用的 2.12.1 版本它通过在任务提交时快照上下文、执行前回填来解决线程池传递问题。如果你也在做 traceId 透传这个依赖值得记一笔。还有一类比忘了写 remove更隐蔽的坑提前 return 漏清理。我们 review 时发现有一处if (whiteList) return;提前返回恰好跳过了后面的remove。这类问题静态扫不出来最好的办法是把set/remove收口到模板里让调用方改不了// 用模板方法把 set/remove 锁死调用方无法漏写 remove public static T, R R with(ThreadLocalT holder, T ctx, FunctionT, R fn) { holder.set(ctx); try { return fn.apply(ctx); // 1. 业务逻辑任意 return/throw 都走 finally } finally { holder.remove(); // 2. 清理固化在模板里调用方改不了 } }这段封装的关键在第 2 行remove被固化在finally中业务方在里面怎么return、throw都会执行从结构上消灭了提前返回漏清理。我们后来把所有ThreadLocal入口都收口到这类模板泄漏类故障基本绝迹。这也顺带回答了前面那个疑问只要对象还被Thread上的ThreadLocalMap强引用着哪怕只有 10MBYoung GC 也回收不了它因为它根本不在年轻代的新建链路里而是挂在久经不衰的线程上。思考题InheritableThreadLocal在线程池下为什么会出现值串号A 请求的 traceId 出现在 B 请求的日志里如果你用原生ThreadLocal存了一个 10MB 的缓存对象且忘了remove它会被 Young GC 回收吗欢迎讨论。

相关新闻

学生党如何低成本选择AI工具:去AIGC与率零对比

学生党如何低成本选择AI工具:去AIGC与率零对比

1. 项目概述:学生党如何低成本选择AI工具去年我在准备毕业论文时,曾经连续两周每天花5小时对比各种AI工具的价格和性能。作为每月生活费只有1500元的穷学生,我深刻理解在预算有限的情况下选择AI工具的纠结。现在市面上主流的"去AIGC&quo…

2026/8/3 6:16:20阅读更多 →
Klick‘r深度解析:Android图像识别自动点击工具架构剖析

Klick‘r深度解析:Android图像识别自动点击工具架构剖析

Klickr深度解析:Android图像识别自动点击工具架构剖析 【免费下载链接】Smart-AutoClicker An open-source auto clicker on images for Android 项目地址: https://gitcode.com/gh_mirrors/smar/Smart-AutoClicker Klickr是一款基于图像识别技术的Android自…

2026/8/3 6:16:20阅读更多 →
MouseClick鼠标连点器:告别重复点击的5分钟终极指南

MouseClick鼠标连点器:告别重复点击的5分钟终极指南

MouseClick鼠标连点器:告别重复点击的5分钟终极指南 【免费下载链接】MouseClick 🖱️ MouseClick 🖱️ 是一款功能强大的鼠标连点器和管理工具,采用 QT Widget 开发 ,具备跨平台兼容性 。软件界面美观 ,操…

2026/8/3 6:14:19阅读更多 →
基于Claude API构建智能体技能:从工具调用到文件处理实战

基于Claude API构建智能体技能:从工具调用到文件处理实战

如果你最近在尝试让大模型帮你写代码、查资料、处理文件,大概率会遇到一个瓶颈:它好像什么都能聊,但一到具体任务就“掉链子”——要么格式不对,要么步骤不全,要么干脆理解错了你的意图。这背后的问题,不是…

2026/8/3 7:22:58阅读更多 →
存储型XSS攻击原理与防御实战:从DVWA靶场到企业级防护

存储型XSS攻击原理与防御实战:从DVWA靶场到企业级防护

1. 项目概述:从一次“诡异”的用户反馈说起几年前,我负责维护一个内部论坛系统。有一天,客服突然收到大量用户投诉,说自己的账号在发一些奇怪的广告贴,内容全是“点击领取百万大奖”之类的垃圾信息。登录后台一看&…

2026/8/3 7:22:58阅读更多 →
从 Prompt 到流水线:WorkBuddy 多专家协同如何把 SEO/GEO 自动化

从 Prompt 到流水线:WorkBuddy 多专家协同如何把 SEO/GEO 自动化

从 Prompt 到流水线:WorkBuddy 多专家协同如何把 SEO/GEO 自动化从工程视角看,WorkBuddy 把"专家"做成可编排的 Agent:每个专家绑定认知框架、工具链与输出模板,多专家通过编排串成 SEO/GEO 流水线(策略→内…

2026/8/3 7:22:58阅读更多 →
《鸣潮》远距离贴图错误修复指南:从DirectX到驱动的系统性排查

《鸣潮》远距离贴图错误修复指南:从DirectX到驱动的系统性排查

在《鸣潮》3.5版本中,部分玩家遇到了一个影响游戏视觉体验的特定问题:MDO显示异常,表现为远距离场景或物体的贴图出现错误、模糊、闪烁或加载不全。这类问题通常与游戏引擎调用图形API、本地图形库状态或特定渲染文件有关,并非游戏…

2026/8/3 7:22:58阅读更多 →
Malware-Bazaar工具集:威胁情报自动化分析与实战指南

Malware-Bazaar工具集:威胁情报自动化分析与实战指南

1. 项目概述:为什么我们需要一个“恶意软件集市”的分析工具集? 在网络安全这个没有硝烟的战场上,情报就是生命线。每天,全球的安全研究团队和安全厂商都会捕获数以百万计的新增恶意样本。这些样本是理解攻击者意图、技战术&#…

2026/8/3 7:22:58阅读更多 →
Gerbv:免费开源的Gerber文件查看器,你的PCB设计质量守护者

Gerbv:免费开源的Gerber文件查看器,你的PCB设计质量守护者

Gerbv:免费开源的Gerber文件查看器,你的PCB设计质量守护者 【免费下载链接】gerbv Maintained fork of gerbv, carrying mostly bugfixes 项目地址: https://gitcode.com/gh_mirrors/ge/gerbv 在电子设计的世界里,PCB制造前的最后一步…

2026/8/3 7:20:57阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/3 0:29:53阅读更多 →
限时公开!某头部SaaS公司内部AI模板工厂架构文档(含5类行业模板源码+性能压测报告)

限时公开!某头部SaaS公司内部AI模板工厂架构文档(含5类行业模板源码+性能压测报告)

更多请点击: https://intelliparadigm.com 第一章:AI模板批量生成的核心价值与落地全景 AI模板批量生成正从实验性工具演进为现代软件工程的关键基础设施。它通过语义理解、上下文感知与结构化约束,将重复性高、模式明确的代码/文档/配置生成…

2026/8/3 0:33:53阅读更多 →
如何快速找回消失的网页:Web Archives浏览器扩展终极指南

如何快速找回消失的网页:Web Archives浏览器扩展终极指南

如何快速找回消失的网页:Web Archives浏览器扩展终极指南 【免费下载链接】web-archives Browser extension for viewing archived and cached versions of web pages, available for Chrome, Edge and Safari 项目地址: https://gitcode.com/gh_mirrors/we/web-a…

2026/8/3 0:20:37阅读更多 →
3个让你工作效率翻倍的Umi-OCR实战技巧:免费离线文字识别完全指南

3个让你工作效率翻倍的Umi-OCR实战技巧:免费离线文字识别完全指南

3个让你工作效率翻倍的Umi-OCR实战技巧:免费离线文字识别完全指南 【免费下载链接】Umi-OCR OCR software, free and offline. 开源、免费的离线OCR软件。支持截屏/批量导入图片,PDF文档识别,排除水印/页眉页脚,扫描/生成二维码。…

2026/8/3 0:00:32阅读更多 →
[具身智能-181]:PC+服务器+具身机器人:构建具身智能从仿真到量产的闭环迭代混合架构

[具身智能-181]:PC+服务器+具身机器人:构建具身智能从仿真到量产的闭环迭代混合架构

PC服务器具身机器人:构建具身智能从仿真到量产的闭环迭代混合架构一、前言:具身智能需要“混合算力闭环系统”传统人工智能依赖云端静态数据集训练,不具备物理交互能力,无法适应真实世界的不确定性。具身智能(Embodied…

2026/8/3 0:00:32阅读更多 →
[具身智能-181]:大分布式通信模型对比:看懂为什么 DDS 是 ROS2 底层通信最优解

[具身智能-181]:大分布式通信模型对比:看懂为什么 DDS 是 ROS2 底层通信最优解

前言构建机器人、具身智能这类分布式实时系统,通信底座直接决定整套系统的实时性、容错性、组网能力。分布式领域长期存在 4 类经典通信架构:点对点模式、Broker 中间代理模式、广播模式、以数据为中心(DDS)模式。很多开发者疑惑&…

2026/8/3 0:00:32阅读更多 →
无损视频剪辑终极指南:如何实现快速高效的多媒体处理

无损视频剪辑终极指南:如何实现快速高效的多媒体处理

无损视频剪辑终极指南:如何实现快速高效的多媒体处理 【免费下载链接】lossless-cut The swiss army knife of lossless video/audio editing 项目地址: https://gitcode.com/gh_mirrors/lo/lossless-cut 在数字媒体创作领域,视频编辑处理的质量损…

2026/8/3 2:32:59阅读更多 →
AI辅助本科论文写作:8大工具评测与高效使用指南

AI辅助本科论文写作:8大工具评测与高效使用指南

1. 本科生论文写作的AI辅助现状本科毕业论文是每个大学生必须跨越的一道坎。记得我当年写论文时,光是文献检索就花了整整两周时间,打印的参考文献堆满了半个书桌。如今AI技术的发展为学术写作带来了革命性变化,合理使用这些工具可以节省80%以…

2026/8/3 2:33:01阅读更多 →
如何快速配置大麦自动抢票系统:从零开始搭建Python抢票助手

如何快速配置大麦自动抢票系统:从零开始搭建Python抢票助手

如何快速配置大麦自动抢票系统:从零开始搭建Python抢票助手 【免费下载链接】ticket-purchase 大麦自动抢票,支持人员、城市、日期场次、价格选择 项目地址: https://gitcode.com/GitHub_Trending/ti/ticket-purchase 还在为抢不到热门演唱会门票…

2026/8/3 2:33:04阅读更多 →