С G1 Collector, один объект массива может быть назначен разным - PullRequest
2 голосов
/ 28 февраля 2020

// VM args: -verbose: g c -Xms20M -Xmx20M -Xmn10M -XX: + PrintGCDetails -XX: SurvivorRatio = 8 -XX: + UseG1G C

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

Выполнить Программа, которую я получил следующий результат:

[G C пауза (G1 Humongous Allocation) (молодой) (начальная отметка), 0,0015208 секунд] ......

куча общее количество первой кучи мусора 20480 КБ, используется 10816 КБ [0x00000007bec00000, 0x00000007bed000a0, 0x00000007c0000000), размер региона 1024 КБ, 2 молодых (2048 КБ), 1 выживший (1024 КБ) совершено 512K, зарезервировано 1048576K

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

The размер массива 2 МБ или 4 МБ, размер региона 1 МБ. Кто-нибудь может мне сказать, почему он использовал 2 молодых региона и 1 выживших?

1 Ответ

1 голос
/ 09 марта 2020

Прежде всего, вы указываете -XX:SurvivorRatio=8, но -XX:+UseAdaptiveSizePolicy включен, так как SurvivorRatio просто игнорируется.

Затем я объяснил, какие строки вы видите немного в этом ответе .

В любом случае, я запустил это с java-13 и Unified Logging, в частности с:

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

, чтобы увидеть, что происходит.

Из журналов я вижу, что в этом коде есть только два цикла G C. На самом деле это всего лишь один, параллельный цикл, но как часть этого цикла, также запускается young G C. Логически, я рассматриваю это как один цикл G C, но G C регистрирует отчеты как два.

Таким образом, ваши журналы будут содержать:

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

И также у вас будет:

[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

Обратите внимание на параллельный цикл спиной к молодому циклу с 1 Young = 1 Eden + 0 Survivors

Позже будет журнал:

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

Как я объяснил в приведенной выше ссылке, следующий цикл молодого Г C намекнут на использование:

11 young regions = 9 Eden + 2 Survivor

Но, как я уже сказал, это только подсказка. Поскольку это огромное распределение , при выходе из него будет фактически использовано меньше областей, как видно из схемы кучи:

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

G C даже не подозревает, что вы выделяете только большие объекты, и он не должен использовать какие-либо молодых регионов вообще, а его эвристика все еще говорит о создании некоторых молодых регионов. Вот почему вы видите эти 2 young, 1 survivor.

Но, если вы запустите свой код достаточно долго:

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

Вы начнете видеть записи вроде:

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

Обратите внимание на 0 young, 0 survivor.

...