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

Java 8 Runtime.getRuntime().maxMemory()计算方法,java-8,jvm,jvm-hotspot,Java 8,Jvm,Jvm Hotspot,代码如下: 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("He

代码如下:

    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-版本 java版本“1.8.0_181” Java(TM)SE运行时环境(build 1.8.0_181-b13) Java HotSpot(TM)64位服务器虚拟机(构建25.181-b13,混合模式) [root@docker-runner-2486794196-0fzm0 docker runner]#java-jar-Xmx1024M-XX:+UnlockExperimentalVMOptions-XX:+UseCGroupMemoryLimitForHeap test.jar 最大运行时间:954728448(910.50米) 非堆:-1(-0.00米) 堆:954728448(910.50米) 池:代码缓存(非堆内存类型)=251658240(240.00 M) 池:元空间(类型非堆内存)=-1(-0.00m) 池:压缩类空间(非堆内存类型)=1073741824(1024.00 M) 池:PS Eden空间(类型堆内存)=355467264(339.00米) 池:PS幸存者空间(类型堆内存)=1048576(1.00米) 池:PS Old Gen(类型堆内存)=716177408(683.00 M)

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

Runtime.maxMemory是910.50M,我想知道这是如何实现的


,但它在JDK8上不起作用。

报告的最大堆和实际最大堆之间的差异源于幸存者空间的假设是基于经验数据的,但没有被证明是故意的特征

我稍微扩展了一下程序(最后是代码)。使用
-Xmx1G-XX:-usepallelgc
jdk6
上运行这个扩展程序给了我很大的帮助

运行时最大值:1037959168(989 MiB)
堆:1037959168(989 MiB)
池:伊甸园空间=286326784(273 MiB)
池:幸存者空间=35782656(34 MiB)
池:终身发电机=715849728(682 MiB)
池:堆内存总量=1037959168(989 MiB)
伊甸园+2*幸存者+终身=1073741824(1024 MiB)
(非堆:省略)
在这里,值匹配。报告的最大大小等于堆空间的总和,因此报告的最大大小和一个幸存者空间大小的总和等于公式
Eden+2*Survivor+Tenured
的结果,即精确的堆大小

我指定
-XX:-UseParallelGC
的原因是,链接答案中的术语“终身”给了我一个关于这个假设来源的提示。正如,当我在Java 6上运行程序而不在我的机器上运行
-XX:-UseParallelGC
时,我得到

运行时最大值:954466304(910 MiB)
堆:954466304(910 MiB)
池:PS Eden空间=335609856(320 MiB)
池:PS幸存者空间=11141120(10 MiB)
池:PS旧Gen=715849728(682 MiB)
池:堆内存总量=1062600704(1013 MiB)
伊甸园+2*幸存者+终身=1073741824(1024 MiB)
(非堆:省略)
这里,报告的最大大小不等于堆内存池的总和,因此“报告的最大大小加上幸存者”公式会产生不同的结果。这些都是相同的值,我使用默认选项从Java8获得,因此您的问题与Java8无关,因为即使在Java6上,当垃圾收集器与链接问答中使用的垃圾收集器不同时,这些值也不匹配

请注意,从Java9开始,
-XX:+UseG1GC
成为默认值,我得到了

运行时最大值:1073741824(1024 MiB)
堆:1073741824(1024 MiB)
池:G1伊甸园空间=未指定/无限制
池:G1幸存者空间=未指定/无限制
池:G1旧Gen=1073741824(1024 MiB)
池:堆内存总量=1073741824(1024 MiB)
伊甸园+2*幸存者+终身=不适用
(非堆:省略)

底线是,假设差值等于幸存者空间的大小,但对于一个特定的(过时的)垃圾收集器来说,这一假设并不成立。但如果适用,公式
Eden+2*Survivor+Tenured
给出了确切的堆大小。对于“垃圾优先”收集器(公式不适用),报告的最大大小已经是正确的值

因此,最好的策略是获取
Eden
幸存者
终身
(又名
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=683MB、NewGen=341MB和MinSurvivorRatio=3。就是

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

如果禁用
-XX:-useAptiveSizePolicy
或将初始堆大小
-Xms
设置为与
-Xmx
相同的值,您将再次看到
Runtime.maxMemory()=OldGen+Eden+Survivor
不确定这是否相关,但您似乎是从docker容器中运行的,您同时使用
-XX:+UseCGroupMemoryLimitForHeap
-Xms
,如图所示,其中一个将覆盖另一个。感谢您的回复!此代码未在docker上运行,将具有相同的结果#java-jar-Xmx1024M test.jar Runtime max:954728448(910.50m)。。。Pool:PS Old Gen(type Heap memory)=716177408(683.00 M)对不起,我已经更新了我的问题。非常感谢。你的回答解决了我的困惑。
Runtime.maxMemory() = 683 + 341 - (341/3) = 910.333 MB