---
order: 12
---
# Java进程内存超出-Xmx指定的大小

## 现象{#trouble}

某系统的实际现象：Tomcat启动时指定了-Xmx=9G，但是使用top命令查看进程的内存占用时，Java进程占用了14.8G，整个服务器的内存是16G，占用了超过90%的内存。
![内存占用大](./images/2021-11-29-10-12-01.jpg)

## 可能原因一：堆外内存泄露{#solution1}

在JDK8及以后的版本-Xmx是指定堆内内存的大小，堆外内存的大小不包含在内，多余的内存都是被堆外内存占用。[OOM（OutOfMemoryError）故障排查](./OOM（OutOfMemoryError）故障排查.md)中的方法用于找出堆内内存问题，无法用来排查堆外内存的问题。堆外内存的占用主要有Metaspace、Direct Buffer，需要找出这2块区域占用的内存大小。

Visual VM是一款可以实时查看JVM内存的工具，包括Metaspace和Direct Buffer。下载地址：<https://visualvm.github.io/download.html>，注意：

- 使用这款工具需要在启动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连接](./images/2021-11-29-09-36-01.png)

- 建立连接之后就可以查看metaspace和direct buffer：
  ![查看Metaspace](./images/2021-11-29-09-42-01.png)
  ![查看Direct Buffer](./images/2021-11-29-09-43-01.png)

JDK默认对Metaspace、Direct Buffer没有大小限制，可以设置Tomcat启动参数来指定大小：`-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:MaxDirectMemorySize=2048m -Dsun.nio.MaxDirectMemorySize=2048m` 。这里指定最大Metaspace为512m基本够了。

## 可能原因二：堆外内存泄露{#solution2}

linux系统有一个经典的64M问题（叫bug更准确）：<https://blog.csdn.net/weixin_35607083/article/details/112100294>。意思是操作系统给进程分配内存时会在一个内存池中分配一块区域，这块区域是64M，如果内存池设置的很大，那么这样的64M区域可能会很多。通过linux的` pmap -x 3563 >pmap.txt`命令可以将指定进程号（比如3563）的内存占用情况输出到一个文本文件中去，下面是一个实际的例子：

![分析日志1](./images/2021-11-29-10-05-01.jpg)

可以看到有大量65536K的内存块，有的一块有65536K，有的是一大一小连续的2块加起来等于65536K，总共多达100多个，内存占用很恐怖。

解决办法是设置linux系统参数`export MALLOC_ARENA_MAX=4`。这个实际项目遇到的问题正是通过这个参数解决。

## 小结{#summary}

排查这里的问题时，关键是要找到是这么大内存由哪些部分组成，哪一部分占用了不合理的空间。实际排查时下面这些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命令输出的内存是一致的。
