
在Java开发中内存问题向来是常见且棘手的挑战。相比于日常编写具有确定性的业务代码内存问题往往具有极强的偶发性和滞后性排查难度呈指数级上升。本文将首先厘清“内存溢出OOM”与“内存泄漏Memory Leak”的核心区别并梳理常规的排查思路。在后续的文章中我还会针对“内存泄漏”这一重灾区提供一套具体、可落地的深度排查实战指南一、 核心区别内存泄漏 (Memory Leak)定义指程序中已经分配的内存由于某些原因如对象引用未释放无法被垃圾回收器GC回收本质是一种状态或过程属于“该还的没还”。就像水桶底有个小洞水内存在不知不觉中慢慢流失导致可用空间越来越少表现程序通常不会立即崩溃而是运行时间越长占用的内存就越多内存溢出 (Out of Memory, OOM)定义指程序在申请内存时系统或JVM没有足够的可用内存供其使用。本质是一种错误或结果属于“借不到了”。就像水桶已经装满了水再也倒不进去了表现程序会直接抛出异常如java.lang.OutOfMemoryError导致程序崩溃或无法响应两者的关系内存泄漏是导致内存溢出的常见原因之一。随着泄漏的累积可用内存越来越少最终在申请新内存时就会触发OOM。但OOM也可以独立发生例如一次性加载一个几百兆的文件而系统堆内存设置较小这会直接导致OOM但代码中并没有内存泄漏二、 排查方法与流程无论是排查内存泄漏还是OOM核心思路都是保留现场 → 分析原因 → 定位代码 → 修复验证1. 紧急止损针对线上OOM当线上服务发生OOM时首先要做的是恢复服务摘流量与重启先从负载均衡中摘掉流量重启实例以恢复服务保留现场重启前务必将.hprof堆快照文件和gc.log拷贝到安全位置否则重启后现场就没了2. 区分OOM类型看错误日志查看日志中java.lang.OutOfMemoryError:后面的具体信息不同类型的OOM指向不同的原因Java heap space堆内存不足Metaspace元空间不足。通常是因为动态生成大量类如反射、CGLib代理或类加载器泄漏Direct buffer memory直接内存不足。NIO操作频繁ByteBuffer.allocateDirect分配的内存未及时释放3. 抓取内存快照事前配置建议在JVM启动参数中加入-XX:HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath/path/to/dump.hprof让JVM在发生OOM时自动生成堆快照手动导出如果未配置自动dump可在OOM后服务未重启前使用jmap -dump:formatb,fileheap.hprof pid命令手动导出在线诊断使用Arthas的memory命令查看内存状态或使用heapdump命令生成堆转储文件无需重启服务即可排查4. 深度分析核心环节使用内存分析工具如Eclipse MAT、JProfiler、VisualVM或Android Profiler打开.hprof文件进行分析查看 Dominator Tree支配树找出占用内存最多的对象内存大户分析引用链Path to GC Roots查看这些大对象是被谁引用的理解它们为何未被回收例如是否被静态集合、长生命周期的缓存持有对比快照捕获不同时间点的堆快照进行对比找出数量持续增长的对象类型这些通常是内存泄漏的根源5. 常见泄漏场景与修复定位到问题对象后需回溯代码进行修复。常见的内存泄漏场景包括静态集合类全局的List、Map等无限制地添加数据且从未清理资源未关闭数据库连接、IO流、网络连接等未在finally块或try-with-resources中正确关闭监听器/回调未注销注册了事件监听器但在组件销毁时忘记移除。不合理缓存使用了没有容量限制和过期策略的缓存如普通的HashMap应改用带有淘汰策略的缓存如 Guava Cache 或 LRU 缓存