你如果线上遇到过 OOM会发现两种人一种人把-Xmx调大先让服务活着另一种人拿到 heapdump定位是哪条引用链不该存在前者能止血后者才能治本。这篇给你一套“从 OOM 到根因”的可执行流程。1. 先判断你要的 dump 是“堆 OOM”还是“元空间 OOM”常见 OOM 信息堆OutOfMemoryError: Java heap space元空间OutOfMemoryError: Metaspace注意heapdump.hprof主要用于分析堆对象元空间问题更常见要看“类数量、类加载器是否泄露”2. 线上生成 heapdump务必谨慎2.1 自动OOM 时自动 dump你可以在启动参数中加入-XX:HeapDumpOnOutOfMemoryError-XX:HeapDumpPath/path/to/dumps优点不需要你手动触发风险dump 文件可能非常大可能对 IO/磁盘造成压力2.2 手动通过 jmap 生成jmap -dump:formatb,fileheap.hprof pid建议大堆生产环境慎用必要时先在低峰期或从副本实例做3. 在分析之前你要先做的“快照判断”heapdump 是静态快照它告诉你谁占内存最大谁在引用谁但它不能直接告诉你“增长趋势”所以最好配合jcmd pid GC.class_histogram当前对象大户业务监控堆使用随时间变化4. MAT 打开 dump 后先看这三个地方4.1 Histogram谁是“大户”实例数最多的类总占用最大的类注意大户不一定是“泄露者”泄露者往往是“持有引用的链路”4.2 Dominator Tree谁在“支配”内存Dominator Tree 是定位泄露最关键的一页“如果这个对象被回收能释放多少内存”你要找Retained Size 异常大的对象4.3 GC Roots从根到可疑对象的引用链MAT 最终要给你一个结论这个对象为什么没被回收通常根因会落在静态集合缓存线程栈引用ThreadLocal监听器/回调未注销5. 一个典型案例静态 Map 缓存导致泄露你在 Dominator Tree 里看到java.util.HashMapretained size 巨大顺着引用链你发现某个static Map持有所有对象这类问题常见解法增加淘汰策略LRU/TTL改成弱引用/软引用要评估缩小缓存粒度6. 另一个典型案例ThreadLocal 没 remove现象线程池线程长期存活ThreadLocalMap 持有大对象排查在 GC Roots 里经常能看到从Thread走到ThreadLocalMap解法try/finally里remove()避免 ThreadLocal 持有大对象7. 你真正该形成的“从 OOM 到根因”的闭环先确认 OOM 类型堆/元空间/线程/直接内存通过class_histogram快速抓大户生成 heapdumpMATHistogram - Dominator Tree - GC Roots 引用链回到代码找到“为什么引用没释放”再改8. 总结heapdump 是定位“为什么没回收”的证据MAT 的关键是 Dominator Tree 与 GC Roots常见根因静态缓存、ThreadLocal、监听器未注销、容器热部署类加载器泄露
HeapDump + MAT:从一次 OOM 到根因定位的完整链路
你如果线上遇到过 OOM会发现两种人一种人把-Xmx调大先让服务活着另一种人拿到 heapdump定位是哪条引用链不该存在前者能止血后者才能治本。这篇给你一套“从 OOM 到根因”的可执行流程。1. 先判断你要的 dump 是“堆 OOM”还是“元空间 OOM”常见 OOM 信息堆OutOfMemoryError: Java heap space元空间OutOfMemoryError: Metaspace注意heapdump.hprof主要用于分析堆对象元空间问题更常见要看“类数量、类加载器是否泄露”2. 线上生成 heapdump务必谨慎2.1 自动OOM 时自动 dump你可以在启动参数中加入-XX:HeapDumpOnOutOfMemoryError-XX:HeapDumpPath/path/to/dumps优点不需要你手动触发风险dump 文件可能非常大可能对 IO/磁盘造成压力2.2 手动通过 jmap 生成jmap -dump:formatb,fileheap.hprof pid建议大堆生产环境慎用必要时先在低峰期或从副本实例做3. 在分析之前你要先做的“快照判断”heapdump 是静态快照它告诉你谁占内存最大谁在引用谁但它不能直接告诉你“增长趋势”所以最好配合jcmd pid GC.class_histogram当前对象大户业务监控堆使用随时间变化4. MAT 打开 dump 后先看这三个地方4.1 Histogram谁是“大户”实例数最多的类总占用最大的类注意大户不一定是“泄露者”泄露者往往是“持有引用的链路”4.2 Dominator Tree谁在“支配”内存Dominator Tree 是定位泄露最关键的一页“如果这个对象被回收能释放多少内存”你要找Retained Size 异常大的对象4.3 GC Roots从根到可疑对象的引用链MAT 最终要给你一个结论这个对象为什么没被回收通常根因会落在静态集合缓存线程栈引用ThreadLocal监听器/回调未注销5. 一个典型案例静态 Map 缓存导致泄露你在 Dominator Tree 里看到java.util.HashMapretained size 巨大顺着引用链你发现某个static Map持有所有对象这类问题常见解法增加淘汰策略LRU/TTL改成弱引用/软引用要评估缩小缓存粒度6. 另一个典型案例ThreadLocal 没 remove现象线程池线程长期存活ThreadLocalMap 持有大对象排查在 GC Roots 里经常能看到从Thread走到ThreadLocalMap解法try/finally里remove()避免 ThreadLocal 持有大对象7. 你真正该形成的“从 OOM 到根因”的闭环先确认 OOM 类型堆/元空间/线程/直接内存通过class_histogram快速抓大户生成 heapdumpMATHistogram - Dominator Tree - GC Roots 引用链回到代码找到“为什么引用没释放”再改8. 总结heapdump 是定位“为什么没回收”的证据MAT 的关键是 Dominator Tree 与 GC Roots常见根因静态缓存、ThreadLocal、监听器未注销、容器热部署类加载器泄露