Das Ausführen des folgenden Codes unter Windows 10 / OpenJDK 11.0.4_x64 erzeugt als Ausgabe used: 197
und expected usage: 200
. Dies bedeutet, dass 200-Byte-Arrays mit einer Million Elementen ca. 200 MB RAM. Alles ist gut.
Wenn ich die Byte-Array-Zuordnung im Code von new byte[1000000]
auf new byte[1048576]
(dh auf 1024 * 1024 Elemente) ändere , wird als Ausgabe used: 417
und ausgegeben expected usage: 200
. Was zum Teufel?
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();
}
}
Wenn ich mit visualvm etwas tiefer schaue, sehe ich im ersten Fall alles wie erwartet:
Im zweiten Fall sehe ich zusätzlich zu den Byte-Arrays die gleiche Anzahl von int-Arrays, die dieselbe Menge an RAM belegen wie die Byte-Arrays:
Diese int-Arrays zeigen übrigens nicht, dass auf sie verwiesen wird, aber ich kann sie nicht mit Müll sammeln ... (Die Byte-Arrays zeigen genau, wo sie referenziert werden.)
Irgendwelche Ideen, was hier passiert?
quelle
int[]
eine große emuliert, um einebyte[]
bessere räumliche Lokalität zu erreichen?Antworten:
Dies beschreibt das Out-of-the-Box-Verhalten des G1-Garbage Collectors, das normalerweise standardmäßig 1 MB "Regionen" verwendet und in Java 9 zu einem JVM-Standard wird. Wenn andere GCs aktiviert sind, ergeben sich unterschiedliche Zahlen.
Ich bin gelaufen
java -Xmx300M -XX:+PrintGCDetails
und es zeigt, dass der Haufen von riesigen Regionen erschöpft ist:Wir möchten, dass unser 1MiB
byte[]
"weniger als die Hälfte der Größe der G1-Region" beträgt, sodass das Hinzufügen-XX:G1HeapRegionSize=4M
eine funktionale Anwendung bietet:Ausführliche Übersicht über G1: https://www.oracle.com/technical-resources/articles/java/g1gc.html
Crushing Detail von G1: https://docs.oracle.com/de/java/javase/13/gctuning/garbage-first-garbage-collector-tuning.html#GUID-2428DA90-B93D-48E6-B336-A849ADF1C552
quelle
long[1024*1024]
dem eine erwartete Verwendung von 1600M mit G1 angegeben wird, variierend um-XX:G1HeapRegionSize
[1M verwendet: 1887, 2M verwendet: 2097, 4M verwendet: 3358, 8M verwendet: 3358, 16M verwendet: 3363, 32M verwendet: 1682]. Mit-XX:+UseConcMarkSweepGC
gebraucht: 1687. Mit-XX:+UseZGC
gebraucht: 2105. Mit-XX:+UseSerialGC
gebraucht: 1698used: 417 expected usage: 400
aber wenn ich ihn entferne,-2
ändert er sich aufused: 470
ungefähr 50 MB, und 50 * 2 Longs sind definitiv viel weniger als 50 MB[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
Es beweist, dass die letzten beiden Longs G1 zwingen, eine weitere 1-MB-Region zuzuweisen, nur um 16 Bytes in zu speichern.