# Java进程内存超出-Xmx指定的大小
# 现象
某系统的实际现象:Tomcat启动时指定了-Xmx=9G,但是使用top命令查看进程的内存占用时,Java进程占用了14.8G,整个服务器的内存是16G,占用了超过90%的内存。
# 可能原因一:堆外内存泄露
在JDK8及以后的版本-Xmx是指定堆内内存的大小,堆外内存的大小不包含在内,多余的内存都是被堆外内存占用。OOM(OutOfMemoryError)故障排查中的方法用于找出堆内内存问题,无法用来排查堆外内存的问题。堆外内存的占用主要有Metaspace、Direct Buffer,需要找出这2块区域占用的内存大小。
Visual VM是一款可以实时查看JVM内存的工具,包括Metaspace和Direct Buffer。下载地址:https://visualvm.github.io/download.html (opens new window),注意:
使用这款工具需要在启动Tomcat时指定启动参数:
-Djava.rmi.server.hostname=172.18.209.109 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9500 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
,这样Tomcat在启动时会启动JMX服务,Visual VM通过JMX服务获取运行时内存信息。这里
java.rmi.server.hostname
是Tomcat的局域网IP,com.sun.management.jmxremote.port
可以随便指定,可以就用9500Visual VM安装好之后建立remote连接,见下图。
建立连接之后就可以查看metaspace和direct buffer:
JDK默认对Metaspace、Direct Buffer没有大小限制,可以设置Tomcat启动参数来指定大小:-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:MaxDirectMemorySize=2048m -Dsun.nio.MaxDirectMemorySize=2048m
。这里指定最大Metaspace为512m基本够了。
# 可能原因二:堆外内存泄露
linux系统有一个经典的64M问题(叫bug更准确):https://blog.csdn.net/weixin_35607083/article/details/112100294 (opens new window)。意思是操作系统给进程分配内存时会在一个内存池中分配一块区域,这块区域是64M,如果内存池设置的很大,那么这样的64M区域可能会很多。通过linux的pmap -x 3563 >pmap.txt
命令可以将指定进程号(比如3563)的内存占用情况输出到一个文本文件中去,下面是一个实际的例子:
可以看到有大量65536K的内存块,有的一块有65536K,有的是一大一小连续的2块加起来等于65536K,总共多达100多个,内存占用很恐怖。
解决办法是设置linux系统参数export MALLOC_ARENA_MAX=4
。这个实际项目遇到的问题正是通过这个参数解决。
# 小结
排查这里的问题时,关键是要找到是这么大内存由哪些部分组成,哪一部分占用了不合理的空间。实际排查时下面这些JDK提供的命令显示内存没有占用那么多,都不到-Xmx指定的大小(9G):
jcmd 2303 GC.heap_info —— 这个可以查看堆信息,执行这个需要指定Tomcat启动参数
-XX:+UnlockDiagnosticVMOptions
jcmd 2303 GC.class_histogram ——这个可以查看class加载情况,执行这个需要指定Tomcat启动参数
-XX:+UnlockDiagnosticVMOptions
jcmd 27264 VM.native_memory detail >native_memory_detail.txt,执行这个需要指定Tomcat启动参数
-XX:NativeMemoryTracking=detail
这间接说明系统不存在内存泄露的问题。换成用pmap命令就能看到真实的内存大小,它输出的内存跟top命令输出的内存是一致的。