【发布时间】:2016-01-23 22:55:17
【问题描述】:
我们有一个长期存在的服务器进程,它在短时间内很少需要大量 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%。再多的点击“Perform 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 一起使用的文档时,我阅读了 the documentation for the java tool/executable 并且在任何地方都没有提到 -XX:MaxHeapFreeRatio 和 -XX:MinHeapFreeRatio 仅适用于 -XX:+UseSerialGC。事实上,固定问题[JDK-8028391] Make the Min/MaxHeapFreeRatio flags manageable 说:
为了使应用程序能够控制允许更多或更少 GC 的方式和时间, 应该制作标志 -XX:MinHeapFreeRatio 和 -XX:MaxHeapFreeRatio 可管理。对这些标志的支持也应该在 默认并行收集器。
已解决问题的comment 说:
对这些标志的支持也被添加到 ParallelGC 作为 自适应大小政策的一部分。
我已经检查过了,fixed issue backported to openjdk-8 中引用的patch 确实包含在我正在使用的 openjdk-8 版本的source package tarball 中。所以它显然应该在“默认并行收集器”中工作,但不像我在这篇文章中展示的那样。我还没有找到任何说明它只能与-XX:+UseSerialGC 一起使用的文档。正如我在这里记录的那样,即使这是不可靠的/冒险的。
我不能让-XX:MaxHeapFreeRatio 和-XX:MinHeapFreeRatio 履行他们的承诺,而不必经历所有这些麻烦吗?
【问题讨论】:
-
搜索是您的朋友 - 答案是“取决于您选择的 GC 算法”:Release java memory for OS at runtime.
-
只是提醒...调用 gc() 不会执行 JVM 垃圾收集器,它只会建议 JVM 执行它。它可能执行,也可能不执行... 另一件事是:要让JVM GC 正常工作,您必须正确编码。我的意思是:注意重Objects的使用,如StringBuffer、Cloneable Objects、不必要的实例、循环内的concat String、Singleton模式等……所有这些东西都会增加堆空间。
-
垃圾收集和向操作系统释放堆是两个不同的操作。堆是线性的;释放堆空间将需要一个专用堆完全为空,或者在堆上进行非常昂贵的碎片整理操作。警告:我不是 Java 专家,但我知道非托管代码中的堆使用情况。
-
虽然这对于一般情况来说可能是正确的,但在我们的例子中,我们会在短时间内分配大量内存并在不久之后释放(大部分)它们。可以看出,使用正确的咒语(例如
-XX:+UseSerialGC和线程)是可能的。我看到了四种可能性: 1) 承认-XX:MaxHeapFreeRatio和-XX:MinHeapFreeRatio有问题,不能像宣传的那样工作。 2) 修复它们的实现 3) 请记住,无论出于何种原因,它们都无法正确实现,并弃用/删除它们。 4) 更改文档以准确反映我的预期。 -
@ErickG.Hagstrom:我的问题从一开始就是“我们如何要求 JVM 将堆内存返回给操作系统?”。我猜答案是“你不能 -
-XX:MaxHeapFreeRatio和-XX:MinHeapFreeRatio不能像宣传的那样工作”。
标签: java memory-management heap-memory