我们有一个长时间运行的服务器进程,在短时间内偶尔需要大量RAM。我们发现一旦JVM从操作系统获取了内存,就不会将其退回给操作系统。如何要求JVM将堆内存返回给操作系统?
通常,对于这种问题的接受答案是使用-XX:MaxHeapFreeRatio
和-XX:MinHeapFreeRatio
。(例如,参见1、2、3、4)。但我们是这样运行Java的:
java -Xmx4G -XX:MaxHeapFreeRatio=50 -XX:MinHeapFreeRatio=30 MemoryUsage
但在VisualVM中仍然可以看到以下内容:
显然,JVM没有遵守-XX:MaxHeapFreeRatio=50
,因为heapFreeRatio非常接近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语言中不行?
编辑:Augusto指出,搜索会让我找到-XX:MaxHeapFreeRatio
和 -XX:MinHeapFreeRatio
,只有在使用-XX:+UseSerialGC
时才能正常工作。我非常兴奋,尝试了一下,惊异于我自己居然没能找到这个选项。是的,它确实可以在我的MemoryUsage.java上工作:
然而,当我尝试将-XX:+UseSerialGC
应用于我们的真实应用程序时,它并没有起到作用:
我发现在一段时间后运行gc()确实有所帮助,因此我创建了一个线程,执行了以下操作:
while (idle() && memoryTooLarge() && ! tooManyAttemptsYet()) {
Thread.sleep(10*1000);
System.gc();
}
这就解决了:
之前我曾在多次尝试中使用过 -XX:+UseSerialGC
和多个 System.gc()
调用来解决问题,但不喜欢需要 GC 线程的方式。而且随着我们的应用程序和 java 的发展,谁知道它是否会继续工作。一定有更好的方法。
是什么逻辑强制我要调用 System.gc()
四次(但不是立即),这些内容在哪里记录?
为了寻找关于 -XX:MaxHeapFreeRatio
和 -XX:MinHeapFreeRatio
只能与 -XX:+UseSerialGC
配合使用的文档,我阅读了java 工具/可执行文件的文档,但未提到 -XX:MaxHeapFreeRatio
和 -XX:MinHeapFreeRatio
仅在使用 -XX:+UseSerialGC
时才有效。事实上,已经修复的问题 [JDK-8028391] Make the Min/MaxHeapFreeRatio flags manageable 表示:
为了让应用程序控制何时以及何时允许更多或更少的 GC,应使标志 -XX:MinHeapFreeRatio 和 -XX:MaxHeapFreeRatio 可管理。这些标志的支持还应该在默认并行收集器中实现。
已修复问题的注释表示:
作为自适应大小策略的一部分,这些标志的支持也添加到了 ParallelGC 中。
我已经检查过,在我使用的 openjdk-8 版本的源软件包 tarball中,参考已修复的问题的补丁确实包含在其中。因此,它应该可以在“默认并行收集器”中工作,但是根据我在这篇文章中所示的情况,它并没有。我还没有找到任何文档表明它只能与 -XX:+UseSerialGC
配合使用。正如我在此处记录的那样,即使如此也不可靠。
难道我不能只通过简单地调整 -XX:MaxHeapFreeRatio
和 -XX:MinHeapFreeRatio
而无需经过所有这些麻烦吗?
-XX:+UseSerialGC
和一个线程),这是可能的。我看到了四种可能性:1)承认-XX:MaxHeapFreeRatio
和-XX:MinHeapFreeRatio
存在缺陷,并且不能按照广告所述工作。2)修复它们的实现。3)认识到无论出于什么原因,它们永远无法被正确实现,并将其弃用/删除。4)更改文档以准确反映我的期望。 - Peter V. Mørch-XX:MaxHeapFreeRatio
和-XX:MinHeapFreeRatio
的效果不像广告中宣传的那样”。 - Peter V. Mørch