请教 Java OOM 及 JVM 相关的问题

67 天前
 superhot

背景

  1. AWS EC2 t3.medium 实例,Amazon Linux 2 系统,4GB 内存。
  2. Java 启动时 Heap 的配置为 -Xmx2847m,大概是给其他服务留 1G 左右内存,其余全部分配给 JVM 。
  3. 结合日志和后台监控发现频繁出现 OOM 导致 Tomcat 重启的问题。

问题

  1. 现在每个实例的平均内存使用率在 93% 左右,此现象是否正常?
  2. 保持当前 EC2 实例配置不变的情况下,给 JVM Heap 分配多大内存比较合适?有什么可以拿来当作判断的依据吗?
  3. 除了 Heap 之外,JVM 还有 Metaspace 、CodeCache 、DirectByteBuffers 等等,这些 Heap 之外的部分可能吃掉多少内存?有什么可以拿来当作判断的依据吗?
  4. 如何分析 OOM 可能的情况?/usr/share/tomcat 目录下有个 hs_err_pid 前缀的日志文件,似乎在 OOM 时会输出相关错误信息,但根本看不懂……

一年后端经验的 CRUD Boy 没系统学过 Java ,不了解 JVM ,突然让去解决 OOM 的问题,实在懵逼,不知从何下手,请各位 Java 大佬们给点建议,救救本菜,谢谢大家!

2777 次点击
所在节点    程序员
43 条回复
zhouhu
67 天前
我猜你虽然设置了最大 Xmx2847m ,但是 JVM 启动的时候并没有申请那么多,当 运行一段时间后 JVM 再去申请内存,操作系统已经没有足够的内存了。

所以设置 Xmx2048m Xms2048m

通过查看不同 region 数量显示,如果是 2048M 还是有空余空间的。

Xmx2847m

total 2847

old: 1733
free: 937
HS: 18
HC:19
E|cs: 122
s|cs: 18
zhouhu
67 天前
我给出的方案是先设置。Xmx2048m Xms2048m 观察
1. 打印详细 GC 日志 -Xlog:gc*=info ,更为详细的是 debug 和 trace
2. -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/heapdump 分析内存镜像。
3. 观察物理机内存使用情况。
superhot
67 天前
@zhouhu 没有参考标准,不知道如何判断业务规模,仅凭个人感觉不是很大,但现有架构已经撑不住了,虽然跑了 7 80 台 `t3.medium` 实例,还是经常超时死锁重启什么的…… 主要原因应该是代码质量差,之前的技术负责人跑路,祖传屎山摇摇欲坠。至于为什么是 2847……我也找不到人问 hhhh
感谢提供思路,我明天再查查 Heap Dump ,印象里好像是有这么个东西的。
chihiro2014
67 天前
用 idea 看看性能分析,该优化优化
zhouhu
67 天前
设置了参数 Xmx2048m Xms2048m ,我觉得基本没啥问题,因为从 region 的分布来看,还有 937 个是空闲。
还有是设置了 Xmx2048m Xms2048m 对系统机器上的其他程序会不会有影响。
superhot
67 天前
@chihiro2014 实不相瞒,用的 VS Code + Java 插件,真是要多难用有多难用……主要是为了用 Copilot 跟 Devcontainer 。
facelezz
67 天前
GC 日志加 MAT 分析下 Heap 应该比较好找吧
facelezz
67 天前
而且你生产 xmx 和 xms 最好设置成一样的
facelezz
67 天前
这个第一眼看上去是物理内存不够 你虽然标定了 xmx2847m 但实际上没有这么多
susuper
67 天前
堆内存调低,加一下-XX:NativeMemoryTracking=summary 里面的数据汇总,就是你程序总共占用的(堆+非堆)。 目前日志看起来是程序占用+系统占用的 超过设备内存大小,无法分配。 可以适当调低堆内存,GC 频繁点,压缩一下内存占用
susuper
67 天前
目前看程序跑起来堆用了 1908 M ,应该是可以压缩的,2048 稳定的话,就试试 1920,1792 等等,只要堆内存不溢出就行
zhouhu
67 天前
Heap:
garbage-first heap total 2916352K, used 1955145K [0x000000074e000000, 0x0000000800000000)
region size 1024K, 141 young (144384K), 18 survivors (18432K)
Metaspace used 80907K, capacity 82397K, committed 83740K, reserved 1124352K
class space used 7289K, capacity 7805K, committed 8316K, reserved 1048576K

从日志来看使用了 1,909M ,一共是 2,848M ,说明此时 JVM 进程已经占用了 1,909M 。剩下的 939 M 是需要向操作系统申请的。申请的时候出现了机器物理内存不足

然后从我统计的 Free region 来看恰好是 937 (与 939 有些许误差)。
此时 cs eden 区: 122 ,survivor cs: 18 ,JVM 完全可以进行垃圾收集。

可以认为是 JVM 给申请新的 eden region 出现了 OOM 。可能是:
1. JVM 想申请新的 region ,尝试 GC ,新的 region 作为 evacuate region 。
2. JVM 认为还有空间剩余,不需要 GC ,直接申请新的 region 。

PS:HS: 18 可以看到大对象有 18 个,并且 HC:19 个,说明大对象可能大对象超过 18 个(大多数可能大于 1M ),可以将 region 的大小设置为 4 M , -XX:G1HeapRegionSize=4M 。

PS: 本人写了一些 G1 文章,欢迎 star https://yoa1226.github.io/
zhouhu
67 天前
GC Heap History (20 events):
Event: 497209.338 GC heap before
{Heap before GC invocations=64918 (full 2):
garbage-first heap total cK, used 2054473K [0x000000074e000000, 0x0000000800000000)
region size 1024K, 142 young (145408K), 23 survivors (23552K)
Metaspace used 80903K, capacity 82391K, committed 83740K, reserved 1124352K
class space used 7288K, capacity 7804K, committed 8316K, reserved 1048576K
}

Event: 497209.794 GC heap before
{Heap before GC invocations=64919 (full 2):
garbage-first heap total 2916352K, used 2089289K [0x000000074e000000, 0x0000000800000000)
region size 1024K, 142 young (145408K), 18 survivors (18432K)
Metaspace used 80903K, capacity 82391K, committed 83740K, reserved 1124352K
class space used 7288K, capacity 7804K, committed 8316K, reserved 1048576K
}

Event: 497210.355 GC heap before
{Heap before GC invocations=64920 (full 2):
garbage-first heap total 2916352K, used 2112841K [0x000000074e000000, 0x0000000800000000)
region size 1024K, 142 young (145408K), 18 survivors (18432K)
Metaspace used 80903K, capacity 82391K, committed 83740K, reserved 1124352K
class space used 7288K, capacity 7804K, committed 8316K, reserved 1048576K
}

从这些日志可以看出 young region 应该是 142 个,超过 142 就触发 GC 。
年轻大小如果没有设置是依据 GC 停顿时间(-XX:MaxGCPauseMillis=200 )自动调整的。142 / 2848 = 4.985 % 约等于 5 %

-XX:G1NewSizePercent=5

-XX:G1MaxNewSizePercent=60

发生 OOM 的时间 young region 恰好是 141 个,说明是 《 JVM 认为还有空间剩余,不需要 GC ,直接申请新的 region 》,但是此时物理机器没有新的连续 1M 的内存就发生 OOM 了。


还有虽然参数设置的是-Xmx2847m ,但实际上 JVM 最大能够申请的是 2848 M 。
zhouhu
67 天前
从统计 E|cs: 122 + s|cs: 18 = 140 来说,当内存不足时,JVM 完全时可以进行 GC 的,但是你设置的 -Xmx2847m 欺骗了 JVM 。

之前为什么顺利进行了 GC ?因为之前 young 达到 142 个时,恰好还有物理内存,可以申请 survivor region 作为 evaluate region 。
chihiro2014
67 天前
@superhot emmm ,idea 也有 copilot 。。。没苦硬吃啊
wangyg
67 天前
1.内存使用率 93%是否正常?
在生产环境中,93%的内存使用率确实偏高。理想情况下,应该保持一定的空闲内存以应对突发的负载增加。通常建议将内存使用率控制在 70-80%左右。高内存使用率可能导致系统性能下降,并增加 OOM 风险。

2.JVM Heap 大小建议
根据提供的信息,你的 EC2 实例有 4GB 内存,当前 JVM 堆大小设置为 2847MB 。考虑到操作系统和其他服务也需要内存,这个设置已经很激进了。建议稍微减小堆大小,例如设置为 2560MB (-Xmx2560m )。这样可以为操作系统和其他进程留出更多空间。


3.JVM 非堆内存使用:
JVM 非堆内存( Metaspace 、CodeCache 、DirectByteBuffers 等)的使用量因应用程序而异。一般来说,这些区域可能占用 200MB-1GB 左右的内存。
判断依据:
- 使用 jconsole 或 jstat 等工具监控实际使用情况
- 分析堆转储( heap dump )文件
- 查看 hs_err_pid 日志中的内存使用信息

4.分析 OOM 的可能情况:
从你提供的 hs_err_pid 日志中,可以看到以下关键信息:
```
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (mmap) failed to map 16384 bytes for committing reserved memory.
```

这表明 JVM 无法为本机内存分配请求分配更多内存。这可能是由于以下原因造成的:

a. 物理内存耗尽
b. 操作系统限制(如 ulimit 设置)
c. 内存碎片化严重

分析建议:
- 使用 jconsole 或 VisualVM 等工具监控 JVM 内存使用情况
- 分析堆转储文件,查找内存泄漏
- 检查 GC 日志,了解垃圾回收情况
- 检查系统日志,查看是否有其他进程占用大量内存
- 考虑使用 JVM 参数如-XX:NativeMemoryTracking=summary 来跟踪本机内存使用

总结,有以下几条建议:
1.调整 JVM 参数:
```
-Xmx2560m -XX:MaxMetaspaceSize=256m -XX:ReservedCodeCacheSize=240m
```

2.启用详细 GC 日志:
```
-Xlog:gc*=info:file=/path/to/gc.log:time,uptime,level,tags:filecount=5,filesize=100m
```

3.使用 jstat 等工具定期监控 JVM 内存使用情况。
4.检查应用程序代码,寻找可能的内存泄漏。
5.优化数据库查询和缓存策略,减少内存压力。
6.考虑使用 JVM 参数如-XX:+HeapDumpOnOutOfMemoryError 来在 OOM 时自动生成堆转储。
MonkeyJon
66 天前
装个 arthas 排查下
cowcomic
66 天前
日志给的听明确的,服务器没可用内存了
The system is out of physical RAM or swap space


/proc/meminfo:
MemTotal: 3964656 kB
MemFree: 114400 kB
MemAvailable: 3092 kB

可用内存才 3M

你看系统日志/var/log/messages ,应该有 JAVA 进程申请内存失败的日志
silencegg
66 天前
if (!recoverable_mmap_error(err)) {
warn_fail_commit_memory(addr, size, exec, err);
vm_exit_out_of_memory(size, OOM_MMAP_ERROR, "committing reserved memory."); //第 3213 , 结合 NMAP ,估计是堆外内存
}

堆外内存不够了
ZZ74
66 天前
Metaspace:

Usage:
Non-class: 72.84 MB capacity, 71.89 MB ( 99%) used, 786.57 KB ( 1%) free+waste, 187.12 KB ( <1%) overhead.
Class: 7.62 MB capacity, 7.12 MB ( 93%) used, 446.66 KB ( 6%) free+waste, 68.88 KB ( <1%) overhead.
Both: 80.47 MB capacity, 79.01 MB ( 98%) used, 1.20 MB ( 1%) free+waste, 256.00 KB ( <1%) overhead.

Virtual space:
Non-class space: 74.00 MB reserved, 73.66 MB (>99%) committed


Metaspace 满了。metaspace 请求内存使用 mmp 的的。
建议二选一
物理内存+1G 或者 Heap 减少一些。从你的 GC 情况看 heap 减个 500MB 左右 应该能跑。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1081255

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX