是,直接看错误栈是否含“java heap space”,再结合堆dump中老年代满、业务对象多来确认;常见于压测复现、响应渐慢、full gc频繁且老年代占用不降。

怎么看是不是堆内存不足导致的 OutOfMemoryError: Java heap space
直接看错误栈里有没有这行——它和 OutOfMemoryError: Metaspace、OutOfMemoryError: GC overhead limit exceeded 不是一回事。前者是对象太多放不下,后两者分别是类元数据撑爆或 GC 太频繁但回收不了多少。如果日志里出现 java.lang.OutOfMemoryError: Java heap space,且堆 dump(用 jmap -dump 或 JVM 参数 -XX:+HeapDumpOnOutOfMemoryError 生成)显示老年代几乎占满、多数对象是业务实体或集合(比如 ArrayList、HashMap),那基本就是堆配置太小或代码里真有泄漏。
常见错误现象:OutOfMemoryError 在压测中稳定复现;应用启动后随请求量增加逐步变慢,最后崩;GC 日志里 Full GC 越来越频繁,每次回收后老年代占用不降反升。
- 别只看 Xmx:-Xms 和 -Xmx 设成一样(如
-Xms2g -Xmx2g),避免堆动态扩容带来的额外 GC 压力 - 别盲目加内存:先确认是不是真缺——用
jstat -gc <pid></pid>看OU(老年代使用量)是否长期 >90%,且OC(老年代容量)没超限 - 32 位 JVM 最大堆不能超过 ~1.5g,64 位才建议设到 4g+;超过 8g 后 CMS 已淘汰,G1 成默认,记得配
-XX:+UseG1GC
怎么快速定位谁在吃堆内存(不用等 OOM 才行动)
等 OOM 再分析 dump 是被动防守。更有效的是在问题初现时就抓现场:用 jmap -histo <pid></pid> 看实时类实例数和总大小,重点关注排前三的业务类;或者用 jcmd <pid> VM.native_memory summary</pid> 辅助排除非堆内存干扰。
使用场景:服务响应变慢但还没挂;监控显示堆使用率持续 >75%;上线新功能后 GC 时间明显上升。
立即学习“Java免费学习笔记(深入)”;
-
jmap -histo:live <pid></pid>比普通histo更准,会触发一次 Full GC 清掉不可达对象再统计 - 注意数组类型:比如
[B是 byte[],[Ljava.lang.String;是 String[],它们常是缓存或日志堆积的源头 - 如果发现大量
java.util.HashMap$Node或java.util.ArrayList,检查是否有无界缓存(没配最大 size 或 TTL)、日志打印了大对象 toString()
堆参数调优时最容易踩的三个坑
不是所有“加大 -Xmx”都能解决问题,有些配置反而让 OOM 来得更快。
- 开 G1 却不配
-XX:MaxGCPauseMillis:G1 默认目标停顿 200ms,若堆大但目标太松,可能长时间卡顿后直接 OOM;建议设为 100~200ms - 用 CMS 时漏配
-XX:CMSInitiatingOccupancyFraction:默认 68%,但老年代增长快时容易来不及回收;设太低(如 50)会导致 GC 频繁,太高(如 90)则易触发 Concurrent Mode Failure → Full GC - 忽略元空间影响:-XX:MaxMetaspaceSize 设太小(如 256m),类加载多时会先爆 Metaspace,继而引发间接堆压力(因为 Full GC 会顺带清理 Metaspace)
线上环境要不要开 -XX:+HeapDumpOnOutOfMemoryError
要,但必须配好路径和权限,否则 dump 写失败,你啥也捞不到。
性能影响很小,但 dump 文件可能很大(几 GB),写入过程会卡住 JVM 几秒——所以不能写到网络盘或 I/O 拥塞的磁盘。
- 务必指定绝对路径:
-XX:HeapDumpPath=/data/dumps/,确保该目录存在且 JVM 进程有写权限 - 加个时间戳避免覆盖:
-XX:HeapDumpPath=/data/dumps/heap_%p_%t.hprof(%p 是 pid,%t 是时间戳) - 别在容器里写到根路径或 /tmp:K8s Pod 重启后丢失;优先写到 emptyDir 或持久卷挂载点
真正难的不是配参数,是拿到 dump 后敢不敢直面自己写的缓存逻辑、日志语句、流式解析里没 close 的 InputStream —— 那些地方,比堆大小更决定 OOM 会不会来。








