Выполнение приведенного ниже кода в Windows 10 / OpenJDK 11.0.4_x64 приводит к выводу used: 197
и expected usage: 200
. Это означает, что 200-байтовые массивы из одного миллиона элементов занимают ок. 200 МБ ОЗУ. Все прекрасно.
Когда я изменяю распределение байтового массива в коде с new byte[1000000]
на new byte[1048576]
(то есть до 1024 * 1024 элементов), он выдает в качестве вывода used: 417
и expected usage: 200
. Какого черта?
import java.io.IOException;
import java.util.ArrayList;
public class Mem {
private static Runtime rt = Runtime.getRuntime();
private static long free() { return rt.maxMemory() - rt.totalMemory() + rt.freeMemory(); }
public static void main(String[] args) throws InterruptedException, IOException {
int blocks = 200;
long initiallyFree = free();
System.out.println("initially free: " + initiallyFree / 1000000);
ArrayList<byte[]> data = new ArrayList<>();
for (int n = 0; n < blocks; n++) { data.add(new byte[1000000]); }
System.gc();
Thread.sleep(2000);
long remainingFree = free();
System.out.println("remaining free: " + remainingFree / 1000000);
System.out.println("used: " + (initiallyFree - remainingFree) / 1000000);
System.out.println("expected usage: " + blocks);
System.in.read();
}
}
Глядя немного глубже с visualvm, я вижу в первом случае все как и ожидалось:
Во втором случае, в дополнение к байтовым массивам, я вижу такое же количество массивов int, занимающих тот же объем ОЗУ, что и байтовые массивы:
Кстати, эти int-массивы не показывают, что на них ссылаются, но я не могу собрать их ... (Байт-массивы прекрасно показывают, где на них ссылаются.)
Есть идеи, что здесь происходит?
источник
int[]
для эмуляции большой объектbyte[]
для лучшей пространственной локализации?Ответы:
Это описывает готовое поведение сборщика мусора G1, который по умолчанию имеет значение «регионы» 1 МБ и становится Java по умолчанию в Java 9. Запуск с другими включенными GC дает различные числа.
Я побежал,
java -Xmx300M -XX:+PrintGCDetails
и это показывает, что куча истощена огромными регионами:Мы хотим, чтобы наш 1MiB
byte[]
был «меньше половины размера области G1», поэтому добавление-XX:G1HeapRegionSize=4M
дает функциональное приложение:Подробный обзор G1: https://www.oracle.com/technical-resources/articles/java/g1gc.html
Дробящая деталь G1: https://docs.oracle.com/en/java/javase/13/gctuning/garbage-first-garbage-collector-tuning.html#GUID-2428DA90-B93D-48E6-B336-A849ADF1C552
источник
long[1024*1024]
который дает ожидаемое использование 1600M с G1, изменяясь на-XX:G1HeapRegionSize
[1M используется: 1887, 2M используется: 2097, 4M используется: 3358, 8M используется: 3358, 16M используется: 3363, 32M используется: 1682]. С-XX:+UseConcMarkSweepGC
используется: 1687. С-XX:+UseZGC
используется: 2105. С-XX:+UseSerialGC
используется: 1698used: 417 expected usage: 400
но если я уберу-2
его , он изменитсяused: 470
примерно до 50 МБ, а 50 * 2 long определенно намного меньше, чем 50 МБ[0.297s][info ][gc,heap ] GC(18) Humongous regions: 450->450
1024 * 1024-2 ->[0.292s][info ][gc,heap ] GC(20) Humongous regions: 400->400
Это доказывает, что последние два длинных принуждают G1 выделять еще одну область