OpenJDK JVM 会把堆内存还给 Linux 吗?

Posted

技术标签:

【中文标题】OpenJDK JVM 会把堆内存还给 Linux 吗?【英文标题】:Will OpenJDK JVM ever give heap memory back to Linux? 【发布时间】: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 不能像宣传的那样工作”。 【参考方案1】:

G1 (-XX:+UseG1GC)、并行清除 (​​-XX:+UseParallelGC) 和 ParallelOld (-XX:+UseParallelOldGC) 确实在堆时返回内存 缩小。我不太确定 Serial 和 CMS,它们并没有缩小 他们在我的实验中堆积如山。

两个并行收集器在收缩之前确实需要多次 GC 堆减小到“可接受”的大小。这是每个设计。他们是 故意持有堆,假设它将被需要 未来。设置标志 -XX:GCTimeRatio=1 将改善 情况有所不同,但仍然需要几次 GC 才能大幅缩小。

G1 非常擅长快速收缩堆,所以对于用例 如上所述,我会说它可以通过使用 G1 并运行来解决 System.gc() 释放所有缓存和类加载器等之后。

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

【讨论】:

感谢@Sammy,提取您提供的链接。我已经尝试过了,确实,-XX:+UseG1GC 确实适用于我上面的示例MemoryUsage.java。但是,-XX:+UseParallelGC-XX:+UseParallelOldGC 都不会导致堆收缩。我不太相信-XX:+UseG1GC 将可靠地为我们的真实应用程序工作。虽然您在技术上确实回答了我的问题,但我会接受。然而,我确实认为真正的答案是“好吧,-XX:MaxHeapFreeRatio-XX:MinHeapFreeRatio 的实现或文档都被彻底破坏了”+ 指向新错误报告的链接。 我清楚地记得这个问题被问到 javaone 的 GC 团队并且答案始终相同,即 Max/MinHeapFreeRatio 标志只是对 jvm 的提示,不多不少。大多数 GC 操作只是尽力而为,不提供任何保证。这是这些标志在 -XX 下暴露的原因之一(相对于 -X 或只是 -)。众所周知,-XX 确实是内部不受支持的标志,Sun/Oracle jvm 开发人员创建这些标志是为了暴露某些行为,这些行为仅在特定条件下有效,有时仅在特定平台和实现上有效。 CMS 应该在它进行完整的 STW 收集时进行,但由于它的设计目标主要是避免 STW 收集,因此它不能可靠地将未使用的内存及时返回给操作系统。 我在 OpenJDK 中运行的应用程序遇到了类似的问题。运行一项大任务后,操作系统内存(win)最终会达到 9GB - 并且永远不会缩小。我添加了 useG1GC 参数,操作系统内存从未超过 700M,并且进程运行速度快了几个 %。

以上是关于OpenJDK JVM 会把堆内存还给 Linux 吗?的主要内容,如果未能解决你的问题,请参考以下文章

JVM中,如果把堆内存参数配置的超过了本地内存,会怎么样

当不再需要时,JVM 是不是会将空闲内存返还给操作系统?

JVM之堆内存

JVM-Ubuntu18.04.1下编译OpenJDK8

Linux构建JVM(HotSpot) 源码调试环境(OpenJDK8)

java basic-GC