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/4 6:23:59 阅读更多 →
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:17:00 阅读更多 →
Java SPI 被 Spring 惯坏后:ServiceLoader 源码里 3 个把人坑哭的实例化细节

Java SPI 被 Spring 惯坏后:ServiceLoader 源码里 3 个把人坑哭的实例化细节

引子:为什么一堆框架都爱用 SPIJava 标准库里有个不起眼的接口加载机制:Service Provider Interface。你在 classpath 下放一个 META-INF/services/全限定接口名 的文件,里面写几个实现类的全限定名,运行时调用 ServiceLoader.loa…

2026/8/3 6:17:00 阅读更多 →

最新新闻

SRWE终极指南:如何突破Windows窗口限制,轻松实现游戏截图和窗口控制

SRWE终极指南:如何突破Windows窗口限制,轻松实现游戏截图和窗口控制

SRWE终极指南:如何突破Windows窗口限制,轻松实现游戏截图和窗口控制 【免费下载链接】SRWE Simple Runtime Window Editor 项目地址: https://gitcode.com/gh_mirrors/sr/SRWE 想要在普通显示器上体验50005000分辨率的游戏截图吗?想摆…

2026/8/4 17:00:42 阅读更多 →
StreamCap:如何轻松实现跨平台直播自动录制,再也不错过精彩内容

StreamCap:如何轻松实现跨平台直播自动录制,再也不错过精彩内容

StreamCap:如何轻松实现跨平台直播自动录制,再也不错过精彩内容 【免费下载链接】StreamCap Multi-Platform Live Stream Automatic Recording Tool | 多平台直播流自动录制客户端 基于FFmpeg 支持监控/定时/转码 项目地址: https://gitcode.com/gh_…

2026/8/4 17:00:42 阅读更多 →
SolidWorks_标准零件库7_标准件装配规范

SolidWorks_标准零件库7_标准件装配规范

标准件装配规范:正确配合定位标准件(同心、重合配合与智能配合)在机械设计与三维建模中,标准件(如螺栓、轴承、销钉)的装配质量直接决定装配体的精度与可维护性。本文从实战出发,系统讲解同心配…

2026/8/4 17:00:42 阅读更多 →
Windows热键冲突检测:Hotkey Detective技术深度解析与实战指南

Windows热键冲突检测:Hotkey Detective技术深度解析与实战指南

Windows热键冲突检测:Hotkey Detective技术深度解析与实战指南 【免费下载链接】hotkey-detective A small program for investigating stolen key combinations under Windows 7 and later. 项目地址: https://gitcode.com/gh_mirrors/ho/hotkey-detective …

2026/8/4 17:00:42 阅读更多 →
NHSE:3步掌握《动物森友会》存档编辑的完整方案

NHSE:3步掌握《动物森友会》存档编辑的完整方案

NHSE:3步掌握《动物森友会》存档编辑的完整方案 【免费下载链接】NHSE Animal Crossing: New Horizons save editor 项目地址: https://gitcode.com/gh_mirrors/nh/NHSE 还在为《集合啦!动物森友会》中漫长的收集过程而烦恼吗?想要快速…

2026/8/4 17:00:42 阅读更多 →
笔记本进水黑屏故障维修:从CPU Vcore短路诊断到主板更换全流程

笔记本进水黑屏故障维修:从CPU Vcore短路诊断到主板更换全流程

这次我们来看一台微星 GP78HX 高端游戏本的维修案例。这台笔记本的故障现象非常典型:进水后不开机,按下电源键指示灯亮,但屏幕黑屏无显示。对于很多用户和维修新手来说,这种“有电无显”的问题往往让人无从下手,不知道…

2026/8/4 16:59:42 阅读更多 →

日新闻

AI Agent白手起家26: 使用标准事件驱动大模型实践

AI Agent白手起家26: 使用标准事件驱动大模型实践

纲要 练习目标:掌握大模型标准事件的调用回顾 LangChain 中的核心标准事件 invokestreambatchastream_eventswith_structured_output 环境准备实战代码:多种事件调用对比 同步调用与流式输出批量处理异步事件流监听结构化输出 运行说明与预期结果总结与扩…

2026/8/4 0:00:40 阅读更多 →
dealsea是什么?跨境卖家必知的美国deal站入门指南

dealsea是什么?跨境卖家必知的美国deal站入门指南

说实话,第一次听说美国这个老牌折扣网站的跨境卖家,十个有八个会问同一个问题:这个平台到底是干嘛的?我见过一个做家居出口的朋友,他在亚马逊上月销二十万美金,却从来没用过它。我给他看了首页——一屏一屏…

2026/8/4 0:01:40 阅读更多 →
清华大学重磅EST:植物自导电闪蒸焦耳热600°C/2600°C两步法!稀土超积累植物秒级转化为CeO₂-石墨烯电催化剂!

清华大学重磅EST:植物自导电闪蒸焦耳热600°C/2600°C两步法!稀土超积累植物秒级转化为CeO₂-石墨烯电催化剂!

通讯作者:邓兵、刘建国通讯单位:清华大学DOI:https://doi.org/10.1021/acs.est.6c00603研究背景稀土元素(REEs)是清洁能源技术与电子器件不可或缺的核心原料,然而传统提取方式依赖能耗高、排放大的采矿与强…

2026/8/4 0:01:40 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/4 13:24:41 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/4 11:41:39 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

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

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

2026/8/4 5:26:40 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/4 13:38:24 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/4 11:09:16 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/4 13:38:40 阅读更多 →