先诊断再调参:通过jstat看OU是否持续攀升判断内存泄漏,EU飙升则短生命周期对象过多,大对象申请失败需查错误栈;必配-XX:+HeapDumpOnOutOfMemoryError获取证据,用jmap-MAT定位static集合、ThreadLocal等泄漏源。

别急着改 -Xmx,先看是不是真缺堆内存——OpenJDK 的 OutOfMemoryError: Java heap space 九成以上不是配置小了,而是对象没被回收、缓存没控制、或单次分配过大。
怎么看是泄漏还是真不够用
核心就盯两个指标:jstat -gc <pid></pid> 输出里的 OU(老年代已用)和 EU(伊甸园已用):
- 如果
OU每次Full GC后都比上一次高 → 基本锁定内存泄漏 - 如果
EU飞快打满、频繁Minor GC,但OU上涨缓慢 → 短生命周期对象太多,GC 压力大,未必泄漏 - 如果错误栈里明确出现
new byte[...]或new ArrayList(...)且参数极大 → 很可能是单点大对象申请失败,不是整体堆不够
抓堆快照前必须加的 JVM 参数
生产环境一旦出错,没快照就等于没证据。启动时务必带上:
-
-XX:+HeapDumpOnOutOfMemoryError:触发 OOM 时自动生成.hprof -
-XX:HeapDumpPath=/path/to/dumps:指定 dump 存放路径,确保目录可写、有空间 -
-Xlog:gc*:file=gc.log:time,uptime,level,tags(OpenJDK 10+)或-XX:+PrintGCDetails -Xloggc:gc.log(旧版):记录 GC 行为,判断回收是否有效 -
-XX:+UseContainerSupport:容器环境下必须加,否则 OpenJDK 可能无视cgroup内存限制,导致被 OS kill
用 jmap 和 MAT 快速定位泄漏根因
拿到 heap.hprof 后别直接打开全量视图,先走三步:
将后端 Java 接口文档转换为前端 TypeScript 类型定义(interface / type)。输入:飞书文档、Spring Boot 接口文档、Swagger/OpenAPI、手写 API 文档等。输出:包含请求参数与响应结果的 .md 文件,含完整 JSDoc 注释。触发关键词:Java 转 TS、接口转换、生成前端类型、接口文档转 TypeScript、DTO/VO/BO 转 interface、后端文档转前端类型、生成接口类型、类型定义生成。
立即学习“Java免费学习笔记(深入)”;
- 用
jmap -histo <pid></pid>快速看类实例数量TOP 20,重点查byte[]、HashMap$Node、LinkedHashMap$Entry、自定义 DTO 列表 —— 数量异常高就是线索 - 用 Eclipse MAT 打开 dump,直接点 “Leak Suspects” 报告,它会自动标出疑似泄漏链;再切到 “Dominator Tree”,按 “Retained Heap” 排序,找占内存最多的对象
- 常见泄漏源要手动验:
static集合没清理、ThreadLocal忘了remove()、监听器注册后没反注册、@Cacheable缓存 key 是大对象且没设maxSize
调堆参数的硬约束和副作用
-Xmx 不是万能解药,加得不对反而让问题更难发现:
- 堆从 2G 加到 8G,泄漏对象积累时间拉长,OOM 从 5 分钟延后到 40 分钟,dump 文件体积暴涨 4 倍,MAT 分析卡死
-
-Xms和-Xmx必须相等(如-Xms4g -Xmx4g),否则堆扩容会触发额外 STW,影响响应稳定性 - 批处理任务可临时加大堆,但必须配
-XX:+HeapDumpOnOutOfMemoryError;日常服务建议压测阶段用小堆(如-Xms512m -Xmx512m)快速暴露泄漏 - 别只盯堆——
OutOfMemoryError: Java heap space发生时,顺手查下元空间:jstat -gcmetacapacity <pid></pid>,排除Metaspace不足误判
真正难的不是加参数,是确认那个 static Map 为什么一直 hold 着 30 万个订单对象,或者线程池里复用的线程为何通过 ThreadLocal 把用户上下文拖成了内存钉子户。这些细节,堆参数永远掩盖不了。

















