# 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可以随便指定,可以就用9500

  • Visual VM安装好之后建立remote连接,见下图。

    建立remote连接

  • 建立连接之后就可以查看metaspace和direct buffer:

    查看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)的内存占用情况输出到一个文本文件中去,下面是一个实际的例子:

分析日志1

可以看到有大量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命令输出的内存是一致的。

是否有帮助?
0条评论
评论