ThreadLocal没remove内存慢慢涨最后OOM了 线上服务跑了三天突然 OOM 挂了。重启三天后又挂了。内存 dump 拉下来用 MAT 一看——堆里有几十万个UserContext对象全被 ThreadLocal 引着GC 收不掉。排查代码里有个全局的用户信息持有者publicclassUserContext{privatestaticfinalThreadLocalUserCURRENT_USERnewThreadLocal();publicstaticvoidset(Useruser){CURRENT_USER.set(user);}publicstaticUserget(){returnCURRENT_USER.get();}}每个请求进来Filter 把用户信息塞进去UserContext.set(currentUser);chain.doFilter(request,response);问题在哪Filter 的doFilter执行完后——没调remove()。线程回到线程池ThreadLocal 里的 User 对象还挂着。下一个请求如果是另一个用户ThreadLocal 被set覆盖旧 User 对象失去了强引用——但 ThreadLocal 内部的 Entry 还在弱引用的 key 被 GC 了value 却因为线程没死而一直存在。这就是 ThreadLocal 内存泄漏的标准剧本。根因ThreadLocal 的内存模型ThreadLocal 不存数据——数据存在 Thread 对象里的ThreadLocalMap。每个 Thread 有一个 Mapkey 是 ThreadLocal 的弱引用value 是你塞进去的对象。Thread → ThreadLocalMap → Entry[弱引用→ThreadLocal, 强引用→User]线程跑完回到线程池线程没死 → ThreadLocalMap 没清 → Entry 还在。GC 回收了 ThreadLocal 对象本身因为是弱引用但valueUser是强引用不收。这个 value 就永远挂在 Thread 的 ThreadLocalMap 里直到线程销毁。正确写法finally 里 removetry{UserContext.set(currentUser);chain.doFilter(request,response);}finally{UserContext.remove();// ✅ 必须清}remove()会删除 ThreadLocalMap 里对应的 Entryvalue 失去引用可以被 GC 回收。任何地方用了ThreadLocal.set()必须在 finally 里调remove()。没有例外。ThreadLocal 另外两个坑坑一线程池复用读到上一个任务的脏数据ThreadLocalStringtraceIdnewThreadLocal();// 任务1traceId.set(req-001);// 忘记 remove// 任务2同一个线程StringidtraceId.get();// → req-001脏数据日志里两个不同请求的 traceId 一样——排查链路全乱。坑二父子线程传递用 InheritableThreadLocal线程池不生效InheritableThreadLocalStringtlnewInheritableThreadLocal();tl.set(parent-value);newThread(()-{System.out.println(tl.get());// ✅ parent-value}).start();InheritableThreadLocal在创建子线程时会把父线程的值拷过去。但线程池里线程是复用的只在第一次创建时拷贝一次后面不会更新。线程池场景用阿里的TransmittableThreadLocalTransmittableThreadLocalStringtlnewTransmittableThreadLocal();tl.set(value);executor.execute(TtlRunnable.get(()-{System.out.println(tl.get());// ✅ 每次都能拿到最新值}));ThreadLocal 三个原则用了set就必须在 finally 里remove线程池场景更要remove——线程不销毁泄漏累积父子线程传递用TransmittableThreadLocal别用InheritableThreadLocal四行代码的事忘了就是 OOM。关于作者无羡独立开发者全栈工程师专注 AI 应用与微服务架构。 分类技术复盘 个人博客 — 更多技术文章✨ 开发者福利整理 — 云服务资源汇总 软件工坊 — 我的技术分享 个人门户 — 独立开发作品全集

相关新闻

最新新闻

SerenityOS 命令行选项解析指南:getopt 与 getopt_long 用法、返回值与底层实现

SerenityOS 命令行选项解析指南:getopt 与 getopt_long 用法、返回值与底层实现

SerenityOS 命令行选项解析指南:getopt 与 getopt_long 用法、返回值与底层实现 【免费下载链接】serenity The Serenity Operating System 🐞 项目地址: https://gitcode.com/GitHub_Trending/se/serenity 导读 本文以 getopt(3) 手册 为核心&a…

2026/10/1 19:32:24
轻量服务器还是ECS?大促云服务器选购与避坑实战指南

轻量服务器还是ECS?大促云服务器选购与避坑实战指南

每年大促节点,群里永远有人在问同一个问题:“38元的轻量服务器到底怎么抢?为什么我每次点进去都是已售罄?68元直购和99元的ECS我到底选哪个?”作为一个常年帮团队和自己采购云服务器的老用户,我太清楚这种纠…

2026/9/30 21:32:07
为 AI 代理的 Review 动作编写 Cedar 审批门控策略:review-agent-governance 策略编写实战指南

为 AI 代理的 Review 动作编写 Cedar 审批门控策略:review-agent-governance 策略编写实战指南

为 AI 代理的 Review 动作编写 Cedar 审批门控策略:review-agent-governance 策略编写实战指南 【免费下载链接】agents Multi-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity 项目地址:…

2026/9/30 19:41:56
PaddleOCR 手写数学公式识别算法 CAN 实战指南:Counting-Aware Network 训练、评估与推理部署

PaddleOCR 手写数学公式识别算法 CAN 实战指南:Counting-Aware Network 训练、评估与推理部署

PaddleOCR 手写数学公式识别算法 CAN 实战指南:Counting-Aware Network 训练、评估与推理部署 【免费下载链接】PaddleOCR Turn any PDF or image document into structured data for your AI. A powerful, lightweight OCR toolkit that bridges the gap between i…

2026/10/1 19:32:23
Spring源码解析:构造器注入的类型转换与候选匹配机制

Spring源码解析:构造器注入的类型转换与候选匹配机制

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 19:32:35
openai-agents-python 多模型接入指南:深入解析 AnyLLMModel 适配层与 any-llm 路由

openai-agents-python 多模型接入指南:深入解析 AnyLLMModel 适配层与 any-llm 路由

openai-agents-python 多模型接入指南:深入解析 AnyLLMModel 适配层与 any-llm 路由 【免费下载链接】openai-agents-python A lightweight, powerful framework for multi-agent workflows 项目地址: https://gitcode.com/GitHub_Trending/op/openai-agents-pyth…

2026/9/30 21:32:11

日新闻

周新闻

月新闻