为什么这个 java 进程不释放内存?

Why does this java process not release memory?

我写了一个 java 应用程序,我有 运行 Fedora 24 下的 java 进程。然后我检查了 jconsole,发现它使用了大约 5 到 10 兆字节的记忆。 垃圾收集的效果在图中也可见。

截图如下:

然后我检查了我的系统监视器,发现同一个进程ID有超过100兆的内存使用。

截图如下:

请告诉我为什么进程不释放未使用的内存?

有什么办法释放吗?

系统监视器中报告的内存是进程使用的所有内存,而不仅仅是 Java 堆。此内存包括:

  • VM 本身的可执行文件,以及它加载的库
  • 为 VM space 工作 作为进程 用于 Hotspot 编译器、GC、IO 缓冲区、屏幕和图形缓冲区、读取 VM 文件之类的
  • Java 堆和其他可报告的内存结构

在您的例子中,10MB 的进程用于存储 Java 堆栈和 Java 对象。另外 90MB 是 Java 程序本身和 VM 内部内存。

这是简短的回答,但还有一个重要的考虑因素 - Java 可以(并且确实)将多余的堆释放回 OS。这是由 -XX:MinHeapFreeRatio-XX:MaxHeapFreeRatio 标志控制的。默认情况下,MaxHeapFreeRatio 为 70%——这几乎与堆图中显示的完全相同(从 6MB 到略低于 10MB 的锯齿状图案)。如果您的应用程序有明显更大的下降,您会在系统监视器中看到 Java 进程的(小)锯齿图案。

为了性能,您通常应该允许 JVM 保留从 GC 中释放的大量堆。为什么?因为我们知道 JVM 将立即需要再次开始分配内存,并且对于 Java 的进程(和 OS)来说,保留它更有效。

所以,总结一下:

  • 系统监视器显示整个 JVM 进程使用的内存
  • Java堆只是进程中使用内存的项目之一
  • JVM 在 GC 后保留(至少部分)它释放的堆(通常)性能更高,因为我们几乎肯定会在接下来的几秒钟内使用它
  • 本例中的 Java 堆在正常范围内振荡,本例中的默认内存配置允许 Java 在 GC
  • 之后保留所有多余的堆

已用 堆和已分配 堆之间存在差异。图表中的蓝线是已用堆 - 有多少堆实际持有对象。没有显示的是分配的堆的大小——这个更大,通常更大,这样 JVM 就可以分配更多的对象,而不用 运行 退出 space 并且不得不返回到OS 以获得更多内存(这很昂贵)。在您的情况下,系统显示的 100mb 中的一部分是 JVM 本身,但其中大部分可能已分配但未使用的堆。

当您 运行 一个 Java 程序没有指定您希望它使用的堆大小时,JVM 将尝试根据您的机器找出一个合理的设置,OS, JVM 版本等。当我只是 运行 在我的机器上使用 16GB RAM 和 Java 8 的简单 Hello World 时,它最初为堆分配了 256mb。显然远远超过它的需要!如果我想强制它使用更少,我可以使用 -Xms 命令行设置初始堆分配和 -Xmx 设置允许的最大值。我的猜测是,如果您设置类似 -Xms20m 的内容,您会发现您的进程使用的内存更少。在 IntelliJ 中,将该设置添加到 运行 配置中的 VM Options 字段。