3

Here is the code:

    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()));
    }  

Run the Code on JDK8 is :

    [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)
    

*Runtime max: 954728448 (910.50 M) *

The Runtime.maxMemory is 910.50M, I want to know how this works out

On JDK7, "Runtime.getRuntime().maxMemory()" = "-Xmx" - "Survivor" , But it does not work on JDK8。

yfk
  • 33
  • 4
  • not sure this is related, but it seems you run this out of a docker container, and you use both `-XX:+UseCGroupMemoryLimitForHeap` and `-Xms`, and as shown [here](https://stackoverflow.com/questions/52555036/do-java-flags-xms-and-xmx-overwrite-flag-xxusecgroupmemorylimitforheap) one will override the other – Eugene Oct 25 '18 at 02:53
  • Thanks for you reply! This code, which is not running on docker, will have the same result #java -jar -Xmx1024M test.jar Runtime max: 954728448 (910.50 M) ... Pool: PS Old Gen (type Heap memory) = 716177408 (683.00 M) – yfk Oct 25 '18 at 05:16
  • Sorry, I have updated my problem – yfk Oct 25 '18 at 09:41

2 Answers2

3

In JDK 8 the formula Runtime.maxMemory() = Xmx - Survivor is still fair, but the trick is how Survivor is estimated.

You haven't set the initial heap size (-Xms), and the Adaptive Size Policy is on by default. This means the heap can resize and heap generation boundaries can move in runtime. Runtime.maxMemory() estimates the amount of memory conservatively, subtracting the maximum possible survivor size from the size of New Generation.

Runtime.maxMemory() = OldGen + NewGen - MaxSurvivor

  where MaxSurvivor = NewGen / MinSurvivorRatio

In your example OldGen = 683 MB, NewGen = 341 MB and MinSurvivorRatio = 3 by default. That is,

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

If you disable -XX:-UseAdaptiveSizePolicy or set the initial heap size -Xms to the same value as -Xmx, you'll see again that Runtime.maxMemory() = OldGen + Eden + Survivor.

apangin
  • 92,924
  • 10
  • 193
  • 247
1

The assumption, that the discrepancy between the reported max heap and the actual max heap stems from the survivor space, was based on empirical data, but has not been proven as intentional feature.

I expanded the program a bit (code at the end). Running this expanded program on JDK 6 with -Xmx1G -XX:-UseParallelGC gave me

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)

Here, the values match. The reported max size is equal to the sum of the heap spaces, so the sum of the reported max size and one Survivor Space’s size is equal to the result of the formula Eden + 2*Survivor + Tenured, the precise heap size.

The reason why I specified -XX:-UseParallelGC was, that the term “Tenured” of the linked answer gave me a hint about where this assumption came from. As, when I run the program on Java 6 without -XX:-UseParallelGC on my machine, I get

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)

Here, the reported max size is not equal to the sum of the heap memory pools, hence the “reported max size plus Survivor” formula produces a different result. These are the same values, I get with Java 8 using default options, so your problem is not related Java 8, as even on Java 6, the values do not match when the garbage collector is different to the one used in the linked Q&A.

Note that starting with Java 9, -XX:+UseG1GC became the default and with that, I get

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)

The bottom line is, the assumption that the difference is equal to the size of the Survivor Space does only hold for one specific (outdated) garbage collector. But when applicable, the formula Eden + 2*Survivor + Tenured gives the exact heap size. For the “Garbage First” collector, where the formula is not applicable, the reported max size is already the correct value.

So the best strategy is to get the max values for Eden, Survivor, and Tenured (aka Old), then check whether either of these values is -1. If so, just use Runtime.getRuntime().maxMemory(), otherwise, calculate Eden + 2*Survivor + Tenured.

The program code:

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);
}
Holger
  • 285,553
  • 42
  • 434
  • 765