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

2022-08-31 17:40:43

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

通常,此类问题的可接受答案是使用 和 。(例如见1234)。但是我们是这样运行java的:-XX:MaxHeapFreeRatio-XX:MinHeapFreeRatio

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

并且仍然在 VisualVM 中看到这一点:

Visual VM memory usage

显然,JVM并不兑现,因为heapFreeRatio非常接近100%,远未接近50%。单击“执行GC”的次数不会将内存返回到操作系统。-XX:MaxHeapFreeRatio=50

内存使用.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的垃圾进出只是浪费资源。如果你不同意,请启发我。

我还用 / 写了一点 memoryUsage.c,它确实会将内存返回到操作系统。所以在C中是可能的。也许不是Java?malloc()free()

编辑:奥古斯托指出,搜索会引导我并且只与.我欣喜若狂,尝试了一下,困惑的是我自己没有找到这个。是的,它确实适用于我的MemoryUsage.java:-XX:MaxHeapFreeRatio-XX:MinHeapFreeRatio-XX:+UseSerialGC

-XX:+UseSerialGC working with simple app

但是,当我尝试使用我们真正的应用程序时,并没有那么多:-XX:+UseSerialGC

-XX:+UseSerialGC not working with real app

一段时间后,我发现gc()确实有帮助,所以我做了一个或多或少做的线程:

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

这就做到了:

GC thread working

实际上,我以前在我的许多实验中已经看到了这种行为和多次调用,但不喜欢需要GC线程。谁知道随着我们的应用程序和Java的发展,这是否会继续工作。一定有更好的方法。-XX:+UseSerialGCSystem.gc()

是什么逻辑迫使我打电话四次(但不是立即),这些东西记录在哪里?System.gc()

为了搜索文档并且只使用,我阅读了java工具/可执行文件的文档,并且在任何地方都没有提到它,并且只适用于.事实上,修复的问题 [JDK-8028391] 使 Min/MaxHeapFreeRatio 标志易于管理说:-XX:MaxHeapFreeRatio-XX:MinHeapFreeRatio-XX:+UseSerialGC-XX:MaxHeapFreeRatio-XX:MinHeapFreeRatio-XX:+UseSerialGC

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

已修复问题的评论说:

对这些标志的支持也已作为自适应大小策略的一部分添加到 ParallelGC 中。

我已经检查过了,在向后移植到openjdk-8的已修复问题中引用的补丁确实包含在我正在使用的openjdk-8版本的源代码包中。因此,它显然应该在“默认并行收集器”中工作,但不像我在这篇文章中演示的那样。我还没有找到任何文档说它应该只与.正如我在这里记录的那样,即使这样也是不可靠/不可靠的。-XX:+UseSerialGC

难道我不能不去经历所有这些箍就能得到并做他们承诺的事情吗?-XX:MaxHeapFreeRatio-XX:MinHeapFreeRatio


答案 1

G1 (-XX:+UseG1GC)、Parallel scavenge (-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


答案 2

推荐