Uruchomienie poniższego kodu w systemie Windows 10 / OpenJDK 11.0.4_x64 powoduje wygenerowanie danych wyjściowych used: 197
i expected usage: 200
. Oznacza to, że 200 bajtów tablic z milionem elementów zajmuje około. 200 MB pamięci RAM. Wszystko w porządku.
Kiedy zmienię przydział tablicy bajtów w kodzie z new byte[1000000]
na new byte[1048576]
(to znaczy na 1024 * 1024 elementów), to generuje jako dane wyjściowe used: 417
i expected usage: 200
. Co za cholera?
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();
}
}
Patrząc nieco głębiej w visualvm, w pierwszym przypadku widzę wszystko zgodnie z oczekiwaniami:
W drugim przypadku oprócz tablic bajtowych widzę tę samą liczbę tablic int zajmujących taką samą ilość pamięci RAM, jak tablice bajtów:
Nawiasem mówiąc, te tablice int nie pokazują, że są do nich odniesienia, ale nie mogę ich wyrzucać do śmieci ... (Tablice bajtów pokazują dobrze, gdzie są odniesienia).
Jakieś pomysły, co się tutaj dzieje?
źródło
int[]
polecenia emulacji dużejbyte[]
dla lepszej lokalizacji przestrzennej?Odpowiedzi:
Opisuje to natychmiastowe zachowanie modułu śmieciowego G1, który zwykle domyślnie przyjmuje 1 MB „regionów” i stał się domyślnym JVM w Javie 9. Uruchamianie z włączonymi innymi GC daje różne liczby.
Pobiegłem
java -Xmx300M -XX:+PrintGCDetails
i pokazuje, że kupa jest wyczerpana przez ogromne regiony:Chcemy, aby nasz 1MiB
byte[]
był „mniejszy niż połowa wielkości regionu G1”, więc dodanie-XX:G1HeapRegionSize=4M
daje funkcjonalną aplikację:Szczegółowy przegląd G1: https://www.oracle.com/technical-resources/articles/java/g1gc.html
Szczegóły kruszenia G1: https://docs.oracle.com/en/java/javase/13/gctuning/garbage-first-garbage-collector-tuning.html#GUID-2428DA90-B93D-48E6-B336-A849ADF1C552
źródło
long[1024*1024]
którego podaje się oczekiwane użycie 1600 M Z G1, zmieniając się o-XX:G1HeapRegionSize
[Użyto 1M: 1887, użyto 2M: 2097, użyto 4M: 3358, użyto 8M: 3358, użyto 16M: 3363, użyto 32M: 1682]. Z-XX:+UseConcMarkSweepGC
stosować: 1687. Z-XX:+UseZGC
stosować: 2105. Z-XX:+UseSerialGC
stosować: 1698used: 417 expected usage: 400
ale jeśli go usunę,-2
zmieni się naused: 470
około 50 MB, a 50 * 2 długości to zdecydowanie mniej niż 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
Dowodzi, że te dwie ostatnie długości zmuszają G1 do przydzielenia kolejnego regionu 1 MB tylko do przechowywania 16 bajtów.