2
votes

//VM args: -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseG1GC

public static void testAllocation() {
     byte[] allocation1, allocation2, allocation3, allocation4;
    allocation1 = new byte[2 * _1MB];
    allocation2 = new byte[2 * _1MB];
    allocation3 = new byte[2 * _1MB];
    allocation4 = new byte[4 * _1MB];
}

public static void main(String[] args) {
    testAllocation();
}

Run the program I got the following result:

[GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0015208 secs] ......

Heap garbage-first heap total 20480K, used 10816K [0x00000007bec00000, 0x00000007bed000a0, 0x00000007c0000000) region size 1024K, 2 young (2048K), 1 survivors (1024K) Metaspace used 3308K, capacity 4496K, committed 4864K, reserved 1056768K class space used 365K, capacity 388K, committed 512K, reserved 1048576K

=================================================================================

The array size are 2MB or 4MB, the region size is 1MB, Could any one tell me why it used 2 young regions and 1 survivors ?

1
Allocated to different what?user207421

1 Answers

1
votes

First of all, you are specifying -XX:SurvivorRatio=8, but -XX:+UseAdaptiveSizePolicy is on, as such SurvivorRatio is simply ignored.

Then, I have explained what some of the lines you see a bit in this answer.

Anyway, I ran this with java-13 and Unified Logging, specifically with:

"-Xlog:heap*=debug" "-Xlog:gc*=debug"

to see what is going on.

From the logs, I see that there are only two GC cycles, as part of this code. Actually it's just one, a concurrent cycle, but as part of that cycle, young GC is triggered also. Logically, I treat that as a single GC cycle, but GC logs reports as two.

As such your logs will contain:

[0.127s][debug][gc,ergo] Request concurrent cycle initiation (requested by GC cause). GC cause: G1 Humongous Allocation

And also, you will have:

[0.133s][info ][gc    ] GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation) 7M->6M(20M) 5.753ms
[0.133s][info ][gc,cpu] GC(0) User=0.01s Sys=0.00s Real=0.01s
[0.133s][info ][gc    ] GC(1) Concurrent Cycle

Notice how the concurrent cycle is piggy-backed off the young cycle.


Now, to answer about the regions.

The GC (0) starts with:

[0.127s][debug][gc,heap] GC(0) Heap before GC invocations=0 (full 0)...
[0.127s][debug][gc,heap] GC(0) region size 1024K, 1 young, 0 survivors

So you start with 1 Young = 1 Eden + 0 Survivors

Later, there will be a log:

[0.133s][info ][gc,heap] GC(0) Eden regions:     1 -> 0 (9)
[0.133s][info ][gc,heap] GC(0) Survivor regions: 0 -> 1 (2)

As I have explained in the link I gave above, the next cycle of young GC will be hinted to use:

11 young regions = 9 Eden + 2 Survivor

But, as I said there, this is only a hint. Since this is a humongous allocation, it will actually use less regions as seen by the heap layout when you exit:

[0.157s][info ][gc,heap,exit]  garbage-first heap   total 20480K....
[0.158s][info ][gc,heap,exit]  region size 1024K, 2 young 1 survivors

The GC has no idea that you only allocate big Objects and it should not use any young regions at all and its heuristics still tell to create some young regions. That is why you see those 2 young, 1 survivor.

But, if you run your code long enough:

public static void main(String[] args) {
    while (true) {
        invokeMe();
    }
}

public static int invokeMe() {
    int x = 1024;
    int factor = 2;
    byte[] allocation1 = new byte[factor * x * x];
    allocation1[2] = 3;
    byte[] allocation2 = new byte[factor * x * x];
    byte[] allocation3 = new byte[factor * x * x];
    byte[] allocation4 = new byte[factor * factor * x * x];

    return Arrays.hashCode(allocation1) ^ Arrays.hashCode(allocation2)
        ^ Arrays.hashCode(allocation3) ^ Arrays.hashCode(allocation4);
}

You will start seeing entries like:

[0.521s][debug][gc,heap] GC(62) Heap before GC invocations=62 
[0.521s][debug][gc,heap] GC(62) region size 1024K, 0 young (0K), 0 survivors (0K)

Notice the 0 young, 0 survivor.