G1GC OutOfMemory слишком рано

Мой тестовый код:

int SIZE = 1900;
int[][] array = new int[SIZE][];
for (int i = 0; i < SIZE; i++) {
  array[i] = new int[1024 * 1024 / 4]; // 1MB
  Thread.sleep(10);
  if (i % 100 == 0 && i != 0) {
    System.out.println(i + "Mb added");
  }
}

Запускаю с аргументами в java 8 -Xmx2048m -XX:+UseG1GC -XX:+PrintGCDetails

И это не удается с OutOfMemory, когда потребляется только 1G.

Heap
garbage-first heap   total 2097152K, used 1048100K [0x0000000080000000, 0x0000000080104000, 0x0000000100000000)
region size 1024K, 1 young (1024K), 0 survivors (0K)
Metaspace       used 3273K, capacity 4496K, committed 4864K, reserved 1056768K
class space    used 358K, capacity 388K, committed 512K, reserved 1048576K

Я вижу, что выделенный размер G1 составляет 2 ГБ, и я полагаю, что JVM пытается выделить больше и терпит неудачу с OOM. Но почему он пытается выделить больше, если половина памяти свободна?

С UseConcMarkSweepGC он работает нормально, и массив был полностью заполнен.


person Eugene To    schedule 29.08.2017    source источник
comment
Как вы рассчитали используемый объем памяти?   -  person Usagi Miyamoto    schedule 29.08.2017
comment
-XX:+PrintGCDetails выводит состояние кучи, как вы можете видеть в моем вопросе total 2097152K, used 1048100K. Я проверял это как на Windows, так и на Linux. Поведение такое же   -  person Eugene To    schedule 29.08.2017
comment
Я имел в виду требуемую память по вашему коду....   -  person Usagi Miyamoto    schedule 29.08.2017
comment
Кроме того, покажите нам исключение, из какой строки оно было выброшено?   -  person Usagi Miyamoto    schedule 29.08.2017
comment
Это была строка array[i] = new int[1024 * 1024 / 4];. Вот это место определенно вызывает OOM. Но обычно OOM может быть выброшен из любого места, которое не имеет ничего общего с основной причиной этого OOM.   -  person Eugene To    schedule 29.08.2017


Ответы (2)


Я почти уверен, что это происходит из-за гигантских распределений.
Если вы добавите эту опцию

-XX:+PrintAdaptiveSizePolicy

вы сможете увидеть, что большинство распределений имеют размер 1048592 байт, что не соответствует ни 50%, ни даже 100% одной области G1 (которая, как видно из вывода, составляет 1024K = 1048576 байт). Я предполагаю, что это означает, что каждый массив занимает как минимум две области. Поскольку это огромное распределение, большая часть свободного пространства во второй области не может быть использована. Это быстро вызывает крайнюю фрагментацию кучи, делая невозможным дальнейшее выделение памяти.

person yegodm    schedule 29.08.2017

Согласен с @yegodm. Решение состоит в том, чтобы увеличить область кучи с помощью -XX:G1HeapRegionSize, чтобы убедиться, что предыдущие объекты Humongous больше не являются Humongous и будут следовать обычному пути распределения. Подробнее о распределении огромных объектов читайте здесь1.

person Fairoz    schedule 30.08.2017