OpenJDK JVM会将堆内存回馈给Linux吗?

我们有一个长期的服务器进程,很less有很短的时间需要大量的RAM。 我们看到,一旦JVM从操作系统获得内存,它就不会将其返回到操作系统。 我们如何让JVM将堆内存返回给操作系统?

通常,这些问题的接受答案是使用-XX:MaxHeapFreeRatio-XX:MinHeapFreeRatio 。 (见例如1,2,3,4 )。 但是我们这样运行java:

 java -Xmx4G -XX:MaxHeapFreeRatio=50 -XX:MinHeapFreeRatio=30 MemoryUsage 

而在VisualVM中仍然可以看到这一点:

可视VM内存使用情况

很明显,JVM并不尊重-XX:MaxHeapFreeRatio=50因为堆自由比率非常接近100%,远不及50%。 没有任何点击“执行GC”将内存返回到操作系统。

MemoryUsage.java:

 import java.util.ArrayList; import java.util.List; public class MemoryUsage { public static void main(String[] args) throws InterruptedException { System.out.println("Sleeping before allocating memory"); Thread.sleep(10*1000); System.out.println("Allocating/growing memory"); List<Long> list = new ArrayList<>(); // Experimentally determined factor. This gives approximately 1750 MB // memory in our installation. long realGrowN = 166608000; // for (int i = 0 ; i < realGrowN ; i++) { list.add(23L); } System.out.println("Memory allocated/grown - sleeping before Garbage collecting"); Thread.sleep(10*1000); list = null; System.gc(); System.out.println("Garbage collected - sleeping forever"); while (true) { Thread.sleep(1*1000); } } } 

版本:

 > java -version openjdk version "1.8.0_66-internal" OpenJDK Runtime Environment (build 1.8.0_66-internal-b01) OpenJDK 64-Bit Server VM (build 25.66-b01, mixed mode) > uname -a Linux londo 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt11-1+deb8u5 (2015-10-09) x86_64 GNU/Linux > lsb_release -a No LSB modules are available. Distributor ID: Debian Description: Debian GNU/Linux 8.2 (jessie) Release: 8.2 Codename: jessie 

我也尝试了OpenJDK 1.7和Sun Java 1.8。 所有行为都是相似的,没有任何内存回到操作系统。

我认为我需要这个,交换和分页不会“解决”这个,因为把磁盘IO分页到接近2GB的垃圾进出只是浪费资源。 如果你不同意,请赐教。

我还用malloc() / free()写了一个小小的memoryUsage.c,它将内存返回给操作系统。 所以它可能在C.也许不是与Java?

编辑:奥古斯托指出,search会导致我-XX:MaxHeapFreeRatio-XX:MinHeapFreeRatio只能使用-XX:+UseSerialGC 。 我欣喜若狂,试了一下,觉得自己没有find。 是的,它与我的MemoryUsage.java一起工作:

-XX:+ UseSerialGC使用简单的应用程序

但是,当我尝试使用-XX:+UseSerialGC与我们的真实应用程序,而不是:

-XX:+ UseSerialGC不能与真正的应用程序一起工作

我发现,gc()在一段时间帮助,所以我做了一个或多或less的线程:

 while (idle() && memoryTooLarge() && ! tooManyAttemptsYet()) { Thread.sleep(10*1000); System.gc(); } 

那就是诀窍:

GC线程工作

实际上,我之前在许多实验中看到过使用-XX:+UseSerialGC和多个System.gc()调用的行为,但不喜欢GC线程的需要。 谁知道这是否会继续工作,因为我们的应用程序和Java的演变。 一定会有更好的办法。

什么是强制我四次(但不是立即)调用System.gc()的逻辑,以及这个东西在哪里logging?

在search-XX:MaxHeapFreeRatio-XX:MinHeapFreeRatio的文档中,只使用-XX:+UseSerialGC ,我阅读了java工具/可执行文件的文档,并且在-XX:MaxHeapFreeRatio-XX:MinHeapFreeRatio只能使用-XX:+UseSerialGC 。 事实上,修正的问题[JDK-8028391]使Min / MaxHeapFreeRatio标志易于pipe理 :

为了使应用程序能够控制如何以及何时允许或多或less的GC,标志-XX:MinHeapFreeRatio和-XX:MaxHeapFreeRatio应该是可pipe理的。 还应该在默认的并行收集器中实现对这些标志的支持。

针对固定问题的评论说:

作为自适应尺寸策略的一部分,对这些标志的支持也被添加到了ParallelGC中。

我已经检查了, 修复的问题中引用的修补程序 backported到openjdk-8确实包含在我使用的openjdk-8版本的源码包tarball中。 所以它应该显然在“默认并行收集器”中工作,但并不像我在这篇文章中演示的那样。 我还没有find任何说它只能用-XX:+UseSerialGC 。 正如我在这里logging的,即使这是不可靠的/冒险的。

我不能只是得到-XX:MaxHeapFreeRatio-XX:MinHeapFreeRatio做他们承诺,而不必经历所有这些箍?

“堆栈收缩时,G1(-XX:+ UseG1GC),并行清除(-XX:+ UseParallelGC)和ParallelOld(-XX:+ UseParallelOldGC)会返回内存,我不太确定Serial和CMS,在我的实验中缩小堆。

两个并行收集器在将堆缩小到“可接受”大小之前确实需要大量的GC。 这是每个devise。 他们正在蓄意坚持,假设将来需要它。 设置标志-XX:GCTimeRatio = 1会稍微改善这种情况,但是仍然需要几个GC来缩小很多。

G1在缩小堆的速度方面非常出色,所以对于上面描述的用例,我可以说通过使用G1和释放所有caching和类加载器之后运行System.gc()等方法可以解决这个问题。

http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6498735

如果有方法(比如m1())在你的应用程序中执行繁重的操作,你可以试试这个:

  1. 而不是调用m1(), serialize方法工作所需的所有对象。
  2. 创build一个de-serialize化前一步中de-serialize对象的主要方法。 并调用m1() – 这个主要方法是程序P1的入口点。
  3. 在m1()完成时,序列化主程序的输出以反序列化。
  4. 使用Runtime or ProcessBuilder作为单独的程序执行P1。

这样,当繁重的提升方法m1()完成时,进程P1将结束,应释放该JVM上的堆。

你的问题退出复杂 – 我会build议一个简单的解决scheme。 从我读过的,你知道如何在C和Java中编码。 可能使用JNA(Java本机访问)或JNI(Java本地接口)可能会解决问题的根源(编码C中的繁重处理部分并从Java调用它)。 另一种方法是用C编写的另一个小程序做繁重的工作 – 你可以从你的java代码中调用这个小程序(最好是在它自己的线程中)。 Java本地访问 Java本地接口