Runtime.getRuntime().maxMemory() 计算方法

Runtime.getRuntime().maxMemory() Calculate Method

代码如下:

    System.out.println("Runtime max: " + mb(Runtime.getRuntime().maxMemory()));
    MemoryMXBean m = ManagementFactory.getMemoryMXBean();

    System.out.println("Non-heap: " + mb(m.getNonHeapMemoryUsage().getMax()));
    System.out.println("Heap: " + mb(m.getHeapMemoryUsage().getMax()));

    for (MemoryPoolMXBean mp : ManagementFactory.getMemoryPoolMXBeans()) {
        System.out.println("Pool: " + mp.getName() +
                " (type " + mp.getType() + ")" +
                " = " + mb(mp.getUsage().getMax()));
    }  

运行 JDK8 上的代码是:

    [root@docker-runner-2486794196-0fzm0 docker-runner]# java -version
    java version "1.8.0_181"
    Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
    Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)
    [root@docker-runner-2486794196-0fzm0 docker-runner]# java -jar -Xmx1024M -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap  test.jar
    Runtime max: 954728448 (910.50 M)
    Non-heap: -1 (-0.00 M)
    Heap: 954728448 (910.50 M)
    Pool: Code Cache (type Non-heap memory) = 251658240 (240.00 M)
    Pool: Metaspace (type Non-heap memory) = -1 (-0.00 M)
    Pool: Compressed Class Space (type Non-heap memory) = 1073741824 (1024.00 M)
    Pool: PS Eden Space (type Heap memory) = 355467264 (339.00 M)
    Pool: PS Survivor Space (type Heap memory) = 1048576 (1.00 M)
    Pool: PS Old Gen (type Heap memory) = 716177408 (683.00 M)
    

*运行最大时间:954728448 (910.50 M) *

Runtime.maxMemory是910.50M,我想知道这是怎么算的

On JDK7, "Runtime.getRuntime().maxMemory()" = "-Xmx" - "Survivor" , 但是在JDK8上是不行的。

报告的最大堆与实际最大堆之间的差异源于幸存者 space 的假设是基于经验数据,但尚未被证明是有意为之的特征。

我稍微扩展了程序(代码在最后)。 运行 这个在 JDK 6-Xmx1G -XX:-UseParallelGC 上的扩展程序给了我

Runtime max: 1037959168 (989 MiB)
Heap: 1037959168 (989 MiB)
Pool: Eden Space = 286326784 (273 MiB)
Pool: Survivor Space = 35782656 (34 MiB)
Pool: Tenured Gen = 715849728 (682 MiB)
Pool: Heap memory total = 1037959168 (989 MiB)
Eden + 2*Survivor + Tenured = 1073741824 (1024 MiB)

(Non-heap: omitted)

此处,值匹配。报告的最大大小等于堆 spaces 的总和,因此报告的最大大小与一个 Survivor Space 的大小之和等于公式的结果 Eden + 2*Survivor + Tenured, 精确的堆大小。

我指定 -XX:-UseParallelGC 的原因是,链接答案中的“终身”一词让我暗示了这个假设的来源。因为,当我 运行 Java 6 上的程序在我的机器上没有 -XX:-UseParallelGC 时,我得到

Runtime max: 954466304 (910 MiB)
Heap: 954466304 (910 MiB)
Pool: PS Eden Space = 335609856 (320 MiB)
Pool: PS Survivor Space = 11141120 (10 MiB)
Pool: PS Old Gen = 715849728 (682 MiB)
Pool: Heap memory total = 1062600704 (1013 MiB)
Eden + 2*Survivor + Tenured = 1073741824 (1024 MiB)

(Non-heap: omitted)

此处,报告的最大大小不等于堆内存池的总和,因此“报告的最大大小加上幸存者”公式会产生不同的结果。这些是相同的值,我用 Java 8 使用默认选项得到,所以你的问题与 Java 8 无关,因为即使在 Java 6 上,这些值在垃圾时也不匹配收集器与链接问答中使用的收集器不同。

请注意,从 Java 9 开始,-XX:+UseG1GC 成为默认设置,因此,我得到

Runtime max: 1073741824 (1024 MiB)
Heap: 1073741824 (1024 MiB)
Pool: G1 Eden Space = unspecified/unlimited
Pool: G1 Survivor Space = unspecified/unlimited
Pool: G1 Old Gen = 1073741824 (1024 MiB)
Pool: Heap memory total = 1073741824 (1024 MiB)
Eden + 2*Survivor + Tenured = N/A

(Non-heap: omitted)

最重要的是,差异等于 Survivor Space 大小的假设仅适用于一个特定的(过时的)垃圾收集器。但是在适用的情况下,公式 Eden + 2*Survivor + Tenured 给出了确切的堆大小。对于“垃圾优先”收集器,在公式不适用的情况下,报告的最大大小已经是正确的值。

所以最好的策略是获取 EdenSurvivorTenured(又名 Old)的最大值,然后检查这些值中的任何一个是否是 -1。如果是,就用Runtime.getRuntime().maxMemory(),否则,计算Eden + 2*Survivor + Tenured.

程序代码:

public static void main(String[] args) {
    System.out.println("Runtime max: " + mb(Runtime.getRuntime().maxMemory()));
    MemoryMXBean m = ManagementFactory.getMemoryMXBean();
    System.out.println("Heap: " + mb(m.getHeapMemoryUsage().getMax()));
    scanPools(MemoryType.HEAP);
    checkFormula();
    System.out.println();
    System.out.println("Non-heap: " + mb(m.getNonHeapMemoryUsage().getMax()));
    scanPools(MemoryType.NON_HEAP);
    System.out.println();
}

private static void checkFormula() {
    long total = 0;
    boolean eden = false, old = false, survivor = false, na = false;
    for(MemoryPoolMXBean mp: ManagementFactory.getMemoryPoolMXBeans()) {
        final long max = mp.getUsage().getMax();
        if(mp.getName().contains("Eden")) { na = eden; eden = true; }
        else if(mp.getName().matches(".*(Old|Tenured).*")) { na = old; old = true; }
        else if(mp.getName().contains("Survivor")) {
            na = survivor;
            survivor = true;
            total += max;
        }
        else continue;
        if(max == -1) na = true;
        if(na) break;
        total += max;
    }
    System.out.println("Eden + 2*Survivor + Tenured = "
        +(!na && eden && old && survivor? mb(total): "N/A"));
}

private static void scanPools(final MemoryType type) {
    long total = 0;
    for(MemoryPoolMXBean mp: ManagementFactory.getMemoryPoolMXBeans()) {
        if(mp.getType()!=type) continue;
        long max = mp.getUsage().getMax();
        System.out.println("Pool: "+mp.getName()+" = "+mb(max));
        if(max != -1) total += max;
    }
    System.out.println("Pool: "+type+" total = "+mb(total));
}

private static String mb(long mem) {
    return mem == -1? "unspecified/unlimited":
        String.format("%d (%d MiB)", mem, mem>>>20);
}

在 JDK 8 中,公式 Runtime.maxMemory() = Xmx - Survivor 仍然是公平的,但诀窍在于如何估计幸存者。

您尚未设置初始堆大小 (-Xms),自适应大小策略默认处于启用状态。这意味着堆可以调整大小并且堆生成边界可以在运行时移动。 Runtime.maxMemory() 保守估计内存量,从新生代的大小中减去最大可能 幸存者大小。

Runtime.maxMemory() = OldGen + NewGen - MaxSurvivor

  where MaxSurvivor = NewGen / MinSurvivorRatio

在您的示例中,默认情况下 OldGen = 683 MB,NewGen = 341 MB 和 MinSurvivorRatio = 3。也就是说,

Runtime.maxMemory() = 683 + 341 - (341/3) = 910.333 MB

如果您禁用 -XX:-UseAdaptiveSizePolicy 或将初始堆大小 -Xms 设置为与 -Xmx 相同的值,您将再次看到 Runtime.maxMemory() = OldGen + Eden + Survivor.