问题现象

前段时间,公司线上的服务器开始出现pod反复重启的现象,但是没有JVM内存泄漏的日志,也没有找到OOM的dump文件,只有一条容器被kill的日志。通过普罗米修斯监控大盘,发现是JVM内存突破了pod的内存限制。


问题分析

现象分析

  1. 硬件配置

我们的应用服务是使用k8s来部署的,应用是基于jdk8的,出现问题的服务pod分配的总数为3台,每个pod分配3G内存和1个高性能CPU,pod的内存required和limit分别为2G和3G。

  1. JVM参数

默认参数,未配置。

  1. JVM内存分析

年轻代正常;老年代占用较多,可能有大对象存在;老年代内存增长同pod增长趋势相同,且有按小时增长迹象,并会触发FullGC。


问题猜测

  1. 随着业务增长,pod内存可能确实不足
  2. JVM默认参数无法有效限制JVM的内存使用
  3. 某个小时任务存在问题,且使用了大对象
  4. 大对象触发FullGC后并没有有效释放内存
  5. 查询k8s官方Issues,是否存在pod与Java版本兼容bug


尝试解决

  1. 增加pod内存,使得required=4G limit=5G
  2. 增加JVM参数限制-Xms=4G -Xmx=4G,堆外内存也要限制,(特别注意jdk8为元空间)-XX:MetaspaceSize=300m -XX:MaxMetaspaceSize=300m
  3. 翻查代码,确实发现某个定时任务存在着大批量的List内存存储,且长时间不释放,但是短时间内没有较好的修改替代方案
  4. 调整年轻代大小-Xmn,使大对象在年轻代就被回收,而不进入老年代
  5. 升级jdk8的小版本,提升jvm对容器限制的感知(这块见参考文档3)


尝试结果

  1. JVM内存增长导致的重启次数变得减少一点,部分FullGC之后pod容器依然健在,但是时间久了依然会被kill重启
  2. 大对象没有被提前回收,依然进入了老年代,且FullGC明显增多


初步结论

  1. 业务增长pod内存确实需要适当增加
  2. JVM参数需要进行手动限制,但是年轻代大小可以不需要调整,反而可以降低FullGC次数
  3. 代码端想更好的优化方法,尽量让数据对象生命周期缩短,但是改动较难
  4. jdk8小版本有对容器限制感知的修复,有一定用处(这块见参考文档3)


问题解决

保持上面的解决方案和参数,我们的服务勉强保持了一段时间,只是相对拉长了容器被重启的时间,告警和重启依然存在,实在令人头疼,很明显还是存在JVM内存泄漏。

后来通过不断的排查监控大盘,我发现堆内内存经过FullGC后,可以做到到达临界线后不再增长,这说明了堆内参数大小和限制都起到了很好的作用;但是要知道,JVM还有一部分叫做堆外内存的东西,参数中限制了我们常知道的MetaSpace,在大盘中我却发现了non-heap memory部分总有比MetaSpace多出的300M,其增长趋势同pod内存一样一致,只是不是那么明显。

这引发了我进一步的思考,堆外内存是不是除了元数据区,还有一块未曾想到的区域?

经过翻查资料:

记一次JVM堆外内存泄漏问题_内存泄漏

发现有一块叫做Direct Memory的区域,这块区域就是我们常提到的NIO为了减少内存拷贝而直接申请的部分,这部分在常见数据库读写客户端中大量存在。

通过排查代码,果不其然,代码除了保留List大对象之外,还大量使用了RedisTemplate对象,这个客户端底层连接基于netty实现,正是NIO那部分。

于是,加上-MaxDirectMemorySize参数后,再次测试,JVM内存稳固被限制,果然没有问题了!而这个参数正是《深入理解JVM虚拟机》前几页所提到的——很多程序员会忽略的一个参数。


问题总结

总的来看,这次的问题DirectMemory是一个主要问题,代码的大对象处理是一个次要问题,其次才应该考虑对JVM的FullGC次数进行优化。

毕竟,JVM是一个复杂的大工程,它已经帮我们很好的完成了对内存的管理;出现了内存泄漏不可怕,要从现象和问题本身出发,要从JVM本身出发,万万不能被所谓的“八股文”和“权威”给束缚住,上来就考虑GC优化往往只会迷失方向。

那些被刻板经验给忽略的,往往就是真相,仅此而已。




参考文档
  1. https://blog.csdn.net/tterminator/article/details/54342666
  2. https://zhuanlan.zhihu.com/p/370241822
  3. https://blog.51cto.com/lookingdream/4046529
  4. https://juejin.cn/post/6844903894863052814
  5. https://www.processon.com/view/link/5b61ea2ae4b0555b39cfa842