《Java 100 天进阶之路》第65篇:OOM与内存泄漏排查(2026版)

📅 2026/7/31 5:14:38
《Java 100 天进阶之路》第65篇:OOM与内存泄漏排查(2026版)
第65篇OOM与内存泄漏排查2026精版系列导航《Java 100 天进阶之路》完整目录 |⬅️ 上一篇第64篇类加载器与热部署 |➡️ 下一篇第66篇JIT编译与性能优化️ 本文阅读地图3 分钟速览第61~64篇搞定了JVM内存、GC、类加载器但线上OOM来了怎么办本篇直击OOM与内存泄漏排查实战。OOM是Java生产环境的“头号杀手”但排查有章可循模块核心问题一句话回答OOM类型OOM有哪几种Heap Space最常见、Metaspace、Direct Buffer、GC Overhead、Unable to create native thread内存泄漏 vs 内存溢出有什么区别泄漏是“慢慢漏”溢出是“瞬间炸”——泄漏是溢出的最常见原因排查流程怎么查看日志 → jstat监控 → jmap dump → MAT分析 → 修复验证五步闭环核心工具用什么工具jstat实时监控、jmap堆快照、MAT深度分析、Arthas生产在线诊断常见泄漏场景哪里最容易漏静态集合、未关闭资源、ThreadLocal、内部类持有外部引用、类加载器泄漏面试最爱问高频考点有哪些见文末 小节核心原则遇到OOM先看日志确认类型再针对性排查不要一上来就dump堆。一、核心知识点1. 内存泄漏 vs 内存溢出术语英文含义类比内存泄漏Memory Leak已分配的内存无法被GC回收可用内存逐渐减少水龙头没关紧一滴一滴漏迟早水漫金山内存溢出OutOfMemoryError (OOM)内存耗尽JVM无法继续分配对象水缸满了继续加水就会溢出核心关系内存泄漏是内存溢出的最常见原因。排查OOM本质上是排查内存泄漏。2. OOM的五种核心类型OOM类型错误信息根本原因排查方向堆内存溢出Java heap space堆内存不足以容纳对象最常见检查大对象、内存泄漏、堆大小元空间溢出Metaspace类元数据占满元空间检查动态类加载、类加载器泄漏直接内存溢出Direct buffer memoryNIO直接内存堆外耗尽检查Netty/DirectByteBuffer使用GC OverheadGC overhead limit exceededGC回收98%内存却腾不出空间基本等于内存泄漏按泄漏排查线程溢出unable to create new native thread线程数超过系统限制检查线程池配置、线程泄漏快速判断OOM日志是第一手线索先看错误信息中的关键词再决定后续排查方向。二、通俗讲解1分钟开心学把JVM内存想象成一个水缸系统堆内存水缸主体装水对象。漏水内存泄漏时水慢慢减少加水太快大对象暴增时水溢出Heap Space OOM。元空间水缸旁边的“目录架”存书的分类信息类元数据。书太多动态生成类太多时目录架被塞满Metaspace OOM。直接内存水缸外的“管道”水流NIO数据从这里快速通过。管道太窄或堵住DirectBuffer过多水流不畅Direct buffer memory OOM。排查就像修水管看水表看日志确认是哪个区域出了问题。监控水位jstat看水位变化趋势。抽水检查jmap dump把水抽出来看里面有什么。实验室分析MAT找出到底是哪个阀门在漏水。修好阀门修复代码把漏水的根源堵住。三、五步排查流程2026实战版第一步看日志——确认OOM类型# 堆内存溢出 java.lang.OutOfMemoryError: Java heap space # 元空间溢出 java.lang.OutOfMemoryError: Metaspace # 直接内存溢出 java.lang.OutOfMemoryError: Direct buffer memory # GC Overhead基本等于内存泄漏 java.lang.OutOfMemoryError: GC overhead limit exceeded⚠️重要OOM时日志中会明确告诉你是哪种类型先看类型再行动不要盲目操作。第二步jstat——实时监控GC状态# 查看GC状态每秒输出一次共10次jstat-gcutilpid100010# 输出示例S0 S1 E O M CCS YGC YGCT FGC FGCT GCT0.000.0045.2398.7692.3489.12123412.34556745.67858.023快速判断标准O老年代使用率长期 95% →内存泄漏FGCFull GC次数每小时涨几百次 →内存泄漏EEden区频繁满 → 对象分配过快YGC次数多但YGCT很低 → 新生代偏小第三步jmap——生成堆快照# 方式1手动dump生产环境谨慎操作建议低峰期jmap-dump:formatb,file/tmp/heap.hprofpid# 方式2OOM时自动dump推荐生产开启-XX:HeapDumpOnOutOfMemoryError-XX:HeapDumpPath/tmp/heap.hprof⚠️重要警告jmap dump时JVM会STWStop-The-World生产环境必须在低峰期操作。推荐配置自动dumpOOM发生时自动生成无需人工介入。OOM自动dump完整配置-XX:HeapDumpOnOutOfMemoryError-XX:HeapDumpPath/var/log/heap.hprof-XX:PrintGCDetails-Xloggc:/var/log/gc.log-XX:UseGCLogFileRotation-XX:NumberOfGCLogFiles10-XX:GCLogFileSize100M第四步MAT——深度分析堆快照1. 导入堆快照将.hprof文件拖入MAT生成“Leak Suspects Report”泄漏检测报告。2. 三大核心视图视图作用使用场景Histogram直方图按类统计实例数和内存占用找出占用内存最多的类Dominator Tree支配树找出保留最多内存的对象根节点定位大对象持有者Path to GC Roots显示对象为何无法被回收的引用链追踪泄漏根源3. 泄漏排查标准流程① 打开Leak Suspects Report红色爆炸图标指向泄漏源头 ② 定位可疑类右键 → Path to GC Roots ③ 选择 exclude all phantom/weak/soft etc. references ④ 找到强引用链定位泄漏代码4. 堆快照损坏的应急处理2026新增高频场景线上dump的hprof文件可能因OOM瞬间进程强制退出、磁盘空间不足等原因被截断。MAT报错HPROF parser encountered a violation时# 临时方案放宽MAT严格模式# Preferences → HPROF Parser → Strictness → 改为 Warning 或 Permissive# 或启动MAT时加参数-DhprofStrictnessWarningtrue⚠️ 强行解析后部分数据会缺失仅应急用正式排查建议重新生成完整堆文件。第五步修复与验证根据MAT分析结果修复代码再次用jstat监控验证泄漏是否已修复。四、常见内存泄漏场景与代码示例场景1静态集合类持有对象// ❌ 错误静态List永不释放publicclassCacheManager{privatestaticListObjectcachenewArrayList();publicvoidadd(Objectobj){cache.add(obj);}// 只增不减}// ✅ 正确使用WeakHashMap或设置缓存上限privatestaticMapString,ObjectcachenewWeakHashMap();// 或使用Caffeine/Guava Cache设置过期时间场景2未关闭的资源// ❌ 错误未关闭连接publicvoidreadFile(Stringpath){FileInputStreamfisnewFileInputStream(path);// 使用fis忘记close}// ✅ 正确try-with-resources自动关闭try(FileInputStreamfisnewFileInputStream(path)){// 使用fis}// 自动关闭场景3ThreadLocal未清理// ❌ 错误线程池中ThreadLocal未removeThreadLocalStringcontextnewThreadLocal();context.set(userId);// 任务结束未调用remove()// 线程池复用 → userId残留且内存泄漏// ✅ 正确try-finally保证removetry{context.set(userId);// 业务逻辑}finally{context.remove();// 必须}特别注意多次热部署后Metaspace持续增长很可能是类加载器泄漏——旧的WebappClassLoader未释放其加载的所有类都无法回收。场景4内部类持有外部类引用// ❌ 错误非静态内部类隐式持有外部类classOuter{classInner{/* 隐式持有Outer.this */}voidmethod(){InnerinnernewInner();// 外部类无法被GC}}// ✅ 正确使用静态内部类staticclassInner{/* 不持有外部引用 */}场景5Druid连接池SQL统计泄漏#Druid配置不当导致SQL统计无限累积 # 错误未限制SQL统计缓存大小# ✅ 正确限制缓存大小关闭不必要的统计spring.datasource.druid:filter.stat.merge-sql:truefilter.stat.slow-sql-millis:5000# 限制连接池监控缓存数量web-stat-filter.session-stat-enable:false五、核心工具链速查工具类型核心命令适用场景jstatJDK内置jstat -gcutil pid 1000实时监控GC判断泄漏趋势jmapJDK内置jmap -dump:formatb,fileheap.hprof pid生成堆快照jhatJDK内置jhat heap.hprof离线分析堆快照Web界面jstackJDK内置jstack pid线程快照排查死锁/阻塞MAT独立工具导入.hprof文件深度泄漏分析、引用链追踪VisualVMJDK内置jvisualvm实时监控、采样分析Arthas阿里开源heapdump、heap、jad生产在线诊断、无需重启工具选型建议开发/测试用VisualVM生产实时监控用jstat深度分析用MAT生产紧急排查用Arthas。六、避坑要点错误/误区后果正确做法遇到OOM第一反应是jmap dump生产环境STW可能引发雪崩先看日志确认OOM类型再行动OOM后暴力扩容加-Xmx掩盖内存泄漏OOM卷土重来先排查泄漏再考虑扩容生产未开启HeapDumpOnOOMOOM后无法分析根因生产环境必须开启MAT解析hprof文件报错就放弃错过排查机会放宽严格模式应急解析jmap dump在生产高峰期执行STW导致接口超时低峰期操作或用自动dumpThreadLocal用完不remove线程池场景内存泄漏try-finally保证remove只看堆内存忽略元空间/直接内存遗漏其他OOM类型根据日志类型针对性排查七、面试高频考点Q1内存泄漏和内存溢出的区别内存泄漏是对象无法被GC回收可用内存逐渐减少内存溢出是内存耗尽JVM无法继续分配对象。内存泄漏是内存溢出的最常见原因。Q2OOM有哪几种常见类型如何排查五种核心类型①Java heap space堆内存溢出→ jstat看GC jmap dump MAT分析②Metaspace元空间溢出→ 检查类加载器泄漏③Direct buffer memory直接内存溢出→ 检查NIO/Netty使用④GC overhead limit exceeded→ 基本等于内存泄漏⑤unable to create new native thread→ 检查线程池配置。Q3生产环境OOM排查的标准流程是什么五步闭环①看日志确认OOM类型②jstat监控GC状态③jmap dump或自动dump生成堆快照④MAT深度分析⑤修复代码并验证。Q4常用的OOM排查工具有哪些①jstat实时GC监控②jmap生成堆快照③MAT深度泄漏分析④VisualVM可视化监控⑤Arthas生产在线诊断。Q5如何防止OOM在生产环境发生① 生产环境强制开启-XX:HeapDumpOnOutOfMemoryError② 开启GC日志③ 配置合理的-Xmx和-Xms④ 代码层面避免静态集合无限增长、资源及时关闭、ThreadLocal及时remove⑤ 定期review和压测。 面试官追问陷阱加分题追问1“MAT分析hprof文件时Path to GC Roots为什么要排除弱引用和软引用” GC Roots路径中弱引用WeakReference和软引用SoftReference在GC时会被回收不会造成内存泄漏。只有强引用链才是泄漏的根本原因。排除弱/软/虚引用后留下的就是真正的泄漏根源。追问2“Metaspace OOM最常见的场景是什么怎么排查” 最常见的是类加载器泄漏——热部署时旧的WebappClassLoader未释放其加载的所有类都无法被GC回收。排查方法用jstat -gc pid看Metaspace使用率持续增长用MAT分析hprof找ClassLoader相关的大对象检查是否有自定义类加载器未正确释放。追问3“OOM自动生成的hprof文件被截断了MAT打不开怎么办” 三种应急方案① 放宽MAT严格模式Preferences → HPROF Parser → Strictness → Warning/Permissive② 添加启动参数-DhprofStrictnessWarningtrue③ 使用其他工具如VisualVM尝试打开。强行解析后部分数据会缺失正式排查建议重新生成完整堆文件。八、练习题分析题某服务jstat显示老年代使用率持续98%以上FGC每小时数百次。这是什么问题下一步应该做什么场景题一个Spring Boot应用频繁热部署后出现OutOfMemoryError: Metaspace分析可能原因和解决方案。代码题下面的代码有什么问题如何修复publicclassUserContext{privatestaticThreadLocalStringuserIdnewThreadLocal();publicstaticvoidsetUserId(Stringid){userId.set(id);}// 使用userId但从未remove}实战题配置生产环境JVM参数要求OOM时自动生成堆快照到/data/dump/heap.hprof并开启GC日志轮转。 你的学习进度当前第65篇 / 共108篇 ·进阶篇JVM调优与故障排查第61~70篇✅ 已完成基础篇44篇 第45~65篇 正在学第65篇⏳ 待学习第66~108篇 完整目录 学习指南 | 订阅本专栏不错过每一篇 下一篇文章预告下一篇《第66篇JIT编译与性能优化》内容简介JIT即时编译器原理、热点代码检测、逃逸分析、栈上分配、锁消除、内联优化以及JVM编译参数调优。JVM调优专题持续深入拿下JIT编译器《Java 100 天进阶之路 | 从入门到上岗就业》每天一篇建议收藏 关注一起100天拿offer 点击关注我更新后第一时间收到推送