云原生环境Jvm内存占用持续高位问题排查

生产内存占用过高问题排查

1、问题描述
一个tomcat应用,部署在云原生集群上,云原生监控显示内存占用率持续偏高,最大占用率、平均占用率都在90%多,但是倒没有OOM什么的。

2、问题复现
凭借对此应用的了解,很快就在测试环境复现了问题。
因为excel导出功能使用了poi的依赖,而这个东西很吃内存(新项目基本不建议使用了)
所以只要多开几个窗口,调用几次下载,内存占用妥妥的就上去了

3、问题排查

首先怀疑是poi有内存泄露,比如workbook/outputStream没有close等
检查代码,发现都有进行close

于是登录到pod内container的控制台,通过jps -l先找到对应java进程的pid
然后用jstat -gc 查看内存占用
用jcmd GC.run 手动调用垃圾回收
经过几轮实验,发现gc后jvm内存占用大幅减少,基本上没占什么内存了,这说明并不存在内存泄露的情况
至少不存在很大的内存泄露,否则回收后内存占用不可能这么小

但是,这个时候诡异的事情出现了!云原生监控平台上内存占用仍然居高不下!
并且也可以从监控平台看到堆内存的情况,的确手动GC后回收得相当干净。
那为什么内存占用仍然这么高?
经咨询监控平台技术人员,内存占用=工作内存/limit,limit即k&s编排文件中声明的最大内存使用量
所以内存占用高,就是因为 工作内存 比较大

用top命令看一下,进程也确实占用了3个G,比较大。(top命令在容器环境下看到的内存总量等是宿主机的,所以不对,但看某一个进程的内存占用还是可以的)

到这里又卡住了

4、真相大白
后面又是一通问deepseek,搜索,总算是破案了。
原来jvm堆内存的GC机制和操作系统的内存分配其实是两码事
具体来说,java进程启动时,根据-Xms配置的堆内存等参数大小,会向操作系统申请相应空间
但是一开始操作系统并不会真的就给你申请的大小,而是分配一个较大的地址空间,但实际只给你一小块内存。
等到实际要用的时候,再通过缺页的中断处理分配内存
所以你会发现 -Xms3G -Xmx3G 这样堆内存初始值、最大值都是3G,但在tomcat启动时也不会真的占用那么多

再来就是,内存分配给jvm后,就是由jvm负责管理了,你堆内存GC,把某块内存标记为空闲,这都是jvm内部的事,操作系统并不知情。
即使jvm已经gc掉了大部分堆内存,但这块内存并不会马上通过free等系统调用函数还给操作系统。什么时候归还,甚至换不换,各个jdk版本并不一样
综上所述,即使jvm GC后没有内存泄露,操作系统可能还是会认为jvm占用了比较大的内存空间,这个一般是没有什么问题的。
但是云原生上也要注意,jvm内存的设置占limit的比例不能太大,如果太大,在其他地方也用到一些内存的时候,操作系统会觉得内存不够用,申请更多内存,
一旦超过limit的限制,pod就会被重启(并且jvm没有oom,你还获取不到堆dump文件)。另外请注意,jvm占用内存并不只是堆,还有方法区、方法栈等。

你可能感兴趣的:(java)