堆内存溢出(java heap space)需先看日志定位类型,再用jstat观察gc异常、jmap生成堆快照,最后用mat分析dominator tree和path to gc roots追溯强引用链,结合代码检查静态集合、threadlocal、资源未关闭等泄漏点。

诊断和解决 JVM 内存溢出与内存泄漏,核心在于“先定位、再归因、后修复”。两者常交织出现——内存泄漏是慢性过程,最终可能引爆内存溢出;而溢出是结果,未必都由泄漏引起。关键不是堆调大就完事,而是看清对象生命周期是否合理、资源是否及时释放、配置是否匹配真实负载。
堆内存溢出(java.lang.OutOfMemoryError: Java heap space)怎么查?
这是最常见也最需警惕的溢出类型,通常意味着对象堆积或内存使用失控。
- 加参数自动生成快照:启动时加上 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump.hprof,OOM 触发时自动保存堆镜像。
- 用 MAT 或 VisualVM 打开 dump 文件,看 Dominator Tree 或 Leak Suspects 报告,重点关注实例数异常多、总大小异常高的类(比如某个 DTO 列表、缓存 Map、日志上下文对象)。
- 结合引用链(Reference Chain)往上追溯:是谁长期持有了这些对象?是不是静态集合、单例服务、未清理的 ThreadLocal、或监听器注册后没注销?
- 别忽略 GC 日志:加 -Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCTimeStamps,观察 Full GC 是否频繁且效果差(回收后堆仍居高不下),这是泄漏的典型信号。
元空间溢出(java.lang.OutOfMemoryError: Metaspace)怎么看?
Java 8+ 中,类元数据不再放在永久代,改用本地内存的元空间。溢出往往和动态类生成强相关。
- 检查是否大量使用 CGLib、Javassist、ASM 等字节码工具(如 Spring AOP 代理、MyBatis 动态 SQL、JSON 序列化框架反射生成类)。
- 搜代码里的 Class.forName()、ClassLoader.loadClass()、defineClass(),尤其在循环或高频路径中调用的,容易反复加载同一类或生成变体类。
- 加参数 -XX:MaxMetaspaceSize=256m(设合理上限,避免无限制膨胀),并配合 -XX:+PrintGCDetails 观察元空间 GC 行为。
- 若确认是类加载器泄漏(如热部署、OSGi 场景),需检查 ClassLoader 是否被业务对象意外持有,导致整个加载器及其加载的所有类无法卸载。
内存泄漏的典型代码陷阱有哪些?
泄漏不等于写错语法,而是逻辑上“忘了放手”。以下几类最易踩坑:
- 静态集合无清理:static List/Map 缓存数据却不设上限、不淘汰、不清理,对象永远可达。
- ThreadLocal 未 remove:在线程池中,线程复用导致前一次 set 的值残留,尤其存储大对象(如用户上下文、数据库连接)时危害极大。
- 资源未关闭:InputStream、Connection、Statement、Channel 等未在 finally 或 try-with-resources 中显式 close,底层 native 资源不释放,间接拖累堆内包装对象无法回收。
- 监听器/回调未注销:向 EventBus、Timer、ServletContext 添加监听后,销毁阶段没对应 remove,造成被监听对象无法释放。
- 缓存无策略:用 HashMap 做缓存却不控制 size、不设 TTL、不用 Weak/SoftReference,缓存越积越多。
日常预防和快速响应怎么做?
靠事后救火不如建立可持续的防护习惯:
- 上线前必加基础监控:用 Prometheus + Grafana 接入 JVM 指标(heap used、metaspace used、gc time、thread count),设置堆使用率 >80% 告警。
- 压测时抓 baseline:对比正常流量与峰值流量下的内存增长趋势,确认是否存在缓慢爬升(泄漏特征)。
- 代码审查盯住“持有关系”:看到 static、ThreadLocal、addListener、put 到全局 Map,就问一句“它什么时候被清?”
- 小改动也跑内存快照:新功能上线后,用 jmap -dump:format=b,file=before.hprof PID 抓一份基线;运行几小时后再抓 after.hprof,用 MAT 对比差异,能快速发现新增泄漏点。
- 别迷信“调大-Xmx”:如果只是把 -Xmx 从 2g 改成 4g 就止住了 OOM,但 GC 频率更高、响应变慢,大概率是掩盖了泄漏,不是解决了问题。











