第一步:保留现场,别急着重启
很多人一收到OOM告警,第一反应是重启服务恢复业务。这没错,业务优先。但重启之前,必须把现场留下来。用jmap -dump:live,format=b,file=heap.hprof <pid>抓取堆快照,同时用jstat -gcutil <pid> 1000持续观察GC状态,再把GC日志、监控曲线、线程栈全部归档。重启只能掩盖问题,现场才是破案的关键。没有dump文件,后面的分析就是无米之炊。亡羊补牢,补的是代码,不是重启。
第二步:GC日志是尸检报告
拿到GC日志后,用GCEasy或GCViewer分析。重点看三个指标:Full GC频率、老年代回收效果、单次停顿时间。如果Full GC后老年代占用几乎不降,说明有对象一直被强引用,典型的内存泄漏。如果Young GC极其频繁且对象晋升老年代速度很快,可能是新生代太小,或者有大对象直接进入老年代。GC日志不会说谎,它忠实记录了堆内存的每一次呼吸。看不懂GC日志,就永远在OOM面前当瞎子。
第三步:堆dump分析,顺藤摸瓜
用MAT打开hprof文件,先看直方图,哪个类的实例数量最多、占用内存最大。再看支配树,找到持有这些对象的GC Root。常见的泄漏元凶就那么几类:静态集合类只增不减、ThreadLocal用完不remove、数据库连接或流未关闭、本地缓存没有淘汰策略。找到GC Root到泄漏对象的引用链,就找到了元凶。这一步需要耐心,但方向对了,剩下的就是时间问题。
第四步:结合代码,对症下药
工具告诉你“是什么”,代码告诉你“为什么”。拿到可疑对象后,看它的类名和字段,回溯代码里哪里创建、哪里添加、哪里移除。用Arthas的watch或trace动态追踪方法的调用参数和返回值。比如发现某个HashMap无限增长,就查是谁在put却从不remove。工具是放大镜,代码才是病灶。不看代码只调参数,是治标不治本。
第五步:修复验证,形成闭环
定位到根因后,修复方案无非三种:代码修复泄漏点、调整堆大小和GC参数、优化对象生命周期。修复完必须压测验证:观察Full GC是否消失、老年代占用是否稳定、TP99是否达标。没有验证的修复,等于没修。把这次OOM的现象、工具、分析、决策、结果整理成案例,下次面试直接讲,比背十道八股文管用。
面试怎么答才算到位
按“保留现场→分析GC日志→分析堆dump→定位代码→修复验证”五步走,每一步带上具体工具和关键指标。再补一个你亲手处理过的真实案例,讲清楚现象、排查路径和最终效果。面试官要的不是标准答案,是你脑子里有没有一张排查地图。把这张地图画出来,offer自然来。