oom应先定位根源而非重启或调大堆内存,需根据错误类型查日志、抓堆快照用mat分析histogram、dominator tree和path to gc roots,并重点检查静态集合、缓存、资源未关闭三类泄漏点,从代码层面优化内存使用。

生产环境出现OOM,别急着重启或调大堆内存——先定位根源。大多数OOM不是“真不够用”,而是对象没被回收或设计不合理导致的内存浪费。
查日志,先确认OOM类型
拿到错误堆栈第一眼要看异常信息:
- java.lang.OutOfMemoryError: Java heap space → 堆内存耗尽,重点查对象泄漏或大数据加载
- java.lang.OutOfMemoryError: Metaspace → 类加载过多,常见于频繁热部署、动态代理、Groovy/SpEL大量使用
- java.lang.OutOfMemoryError: GC overhead limit exceeded → GC花98%时间却只回收不到2%内存,说明老年代已满且对象难以释放
- java.lang.OutOfMemoryError: Direct buffer memory → Netty、NIO直接内存未释放,或堆外内存配置过小
抓快照,用MAT快速定位大对象
确保JVM启动时加了这两个参数,OOM发生时自动落盘:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/dump/
用Eclipse MAT打开hprof文件,重点关注:
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
-
Histogram:看哪个类实例最多、占内存最大(比如
byte[]、String、自定义DTO) - Dominator Tree:找出“谁占了最多内存”,按Retained Heap排序
- Path to GC Roots:右键可疑对象 → “Show outgoing references” → 找出强引用链,定位是谁在长期持有它(如static Map、ThreadLocal、缓存未清理)
盯住三类高频泄漏点
不用猜,先检查这些地方:
-
静态集合无限制增长:如
private static List<object> cache = new ArrayList();</object>,没清空逻辑,每次请求都add - 缓存未设上限或过期策略:ConcurrentHashMap做本地缓存,但key不断进、value不淘汰,尤其带前缀索引(如用户名补全场景,1万个用户生成6万份重复DTO)
- 资源未关闭 + 引用未清理:数据库连接/流未用try-with-resources;监听器注册后没反注册;ThreadLocal变量用完没remove,尤其在线程池复用场景下会累积
改代码,从源头控制内存膨胀
修复不是“加-Xmx”,而是让内存使用更合理:
- 用
SoftReference或WeakReference做缓存,内存紧张时可自动回收 - 大数据查询加分页或流式处理,避免
list = jdbcTemplate.query(...)一次性拉10万条 - 重复对象共享引用,比如补全索引中多个key指向同一份UserDTO,而不是每个key都new一份
- 定时任务或后台线程里操作集合,加清理逻辑:
cache.clear()或LRU淘汰
不复杂但容易忽略,关键是把“谁在留着对象不放”找出来,而不是等它满了再扩容。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










