Skip to content

记一次线上 JVM OutOfMemory 的异常排查

背景

小孟同学今天被拉去帮忙看一个客户自己开发的系统出现的问题去了。

一个文件上传的功能,他在自己本地怎么测试都是没问题的,但是生产环境上传 100MB 文件就 heap OutOfMemory 了,于是找到了我。

开始排查

  1. 首先检查了 SpringBoot 上传文件的配置参数,设置为 1GB,和环境上一致,没问题;
  2. 检查服务器可用内存是否足够,发现服务器配置已经比较牛逼了,内存有 256GB(注意:不是磁盘大小),可用内存还有 200 多 GB。
  3. 检查 JVM 内存分配启动参数,发现他就通过 java -jar application.jar 的命令启动的,按照 JDK 默认内存分配规则来讲,应该是没问题的。
  4. 怀疑是否有内存泄漏,但按照上面的参数,200 多 GB 物理内存,以及用户量很少,又简单看了下代码,没什么特别的地方,即使有内存泄漏,不会刚一启动服务就去测试,就会遇到这个问题。所以,肯定不是内存泄漏导致。
  5. 为了确保 JVM 内存足够,还是用 jps -l/jinfo pid/jmap -heap pid 等命令查看下实际堆内存,发现总共只有 256MB!可用堆内存只用 51MB! Why? 按照上面的参数,不应该呀!先不管了,反正 256MB 的 JVM 确实比较小,可用堆内存只剩下 51MB,上传 100MB 文件那肯定会内存溢出。
  6. 既然问题原因找到了,那就加 JVM 堆内存呗。于是 java -jar -Xms2048MB -Xmx8192MB application.jar
  7. 又报错了。Error occurred during initialization of VM; Could not reserve enough space 8192MB for object heap.
  8. 为什么?本地测试都是可以的!生产环境就不行。改小点试试。
  9. 于是 java -jar -Xms512MB -Xmx2048MB application.jar。但是很遗憾,依旧报 Could not reserve enough space 2048MB for object heap.
  10. 到这里我大概知道什么原因了,肯定是 JDK 的问题,不然没理由了,本地测试是好的,生产环境是坏的,我认为就是环境问题了。
  11. 看一下 JDK 版本,生产环境用到的 JDK8 32 位系统。好吧,解密了。
  12. 理论上 32 位的 JVM 堆内存可以到达 2^32,即 4GB, 但实际上会比这个小很多。不同操作系统之间不同, 如 Windows 系统大约 1.5 GB, Solaris 大约 3GB。
  13. 明白了,服务器是 Windows Server 系统的,所以最大支持的 JVM 堆内存大约 1.5GB,因此,即使我给了 2048MB,依然也会报错。
  14. 啥年代了,都用 JDK8 了为啥要用 32 位的。既然原因找到了,那就换个 64 位的 JDK 吧。
  15. 然后启动 java -jar -Xms2048MB -Xmx8192MB application.jar 嗯,不报错了。
  16. 测试文件上传,上传成功,问题解决了。

总结

  • 排查 JVM 相关的问题时,还是仔细一些。尽管我一上去就 java -version 查看了 JDK 版本,但只重点看了是 JDK8 就觉得没问题,而忽略了它是 32 位的事实。
  • jps/jinfo/jmap/jstack 等命令还是挺有用的,在没法用 Arthas 的情况下,这些命令能够解决大问题。