Menjalankan kode di bawah ini pada Windows 10 / OpenJDK 11.0.4_x64 menghasilkan sebagai keluaran used: 197
dan expected usage: 200
. Ini berarti bahwa array 200 byte dari satu juta elemen membutuhkan sekitar. RAM 200MB. Semuanya baik-baik saja.
Ketika saya mengubah alokasi array byte dalam kode dari new byte[1000000]
ke new byte[1048576]
(yaitu, ke 1024 * 1024 elemen), itu menghasilkan output used: 417
dan expected usage: 200
. Apa apaan?
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();
}
}
Terlihat sedikit lebih dalam dengan visualvm, saya melihat dalam kasus pertama semuanya seperti yang diharapkan:
Dalam kasus kedua, selain array byte, saya melihat jumlah array int yang sama dengan jumlah RAM yang sama dengan array byte:
Array int ini, omong-omong, tidak menunjukkan bahwa mereka direferensikan, tapi saya tidak bisa mengumpulkan sampah ... (Array byte menunjukkan dengan baik di mana mereka direferensikan.)
Ada ide apa yang terjadi di sini?
sumber
int[]
untuk meniru besarbyte[]
untuk lokalitas spasial yang lebih baik?Jawaban:
Apa yang dijelaskan ini adalah perilaku out-of-the-box dari pengumpul sampah G1 yang umumnya default ke 1MB "region" dan menjadi default JVM di Java 9. Menjalankan dengan GC lain yang diaktifkan memberikan angka yang bervariasi.
Saya berlari
java -Xmx300M -XX:+PrintGCDetails
dan itu menunjukkan tumpukan habis oleh daerah humongous:Kami ingin 1MiB kami
byte[]
menjadi "kurang dari setengah ukuran wilayah G1" sehingga menambahkan-XX:G1HeapRegionSize=4M
memberikan aplikasi fungsional:Ikhtisar mendalam tentang G1: https://www.oracle.com/technical-resources/articles/java/g1gc.html
Detail perincian G1: https://docs.oracle.com/en/java/javase/13/gctuning/garbage-first-garbage-collector-tuning.html#GUID-2428DA90-B93D-48E6-B336-A849ADF1C552
sumber
long[1024*1024]
yang memberikan perkiraan penggunaan 1600M Dengan G1, bervariasi menurut-XX:G1HeapRegionSize
[1M digunakan: 1887, 2M digunakan: 2097, 4M digunakan: 3358, 8M digunakan: 3358, 16M digunakan: 3363, 32M digunakan: 1682]. Dengan-XX:+UseConcMarkSweepGC
bekas: 1687. Dengan-XX:+UseZGC
bekas: 2105. Dengan-XX:+UseSerialGC
bekas: 1698used: 417 expected usage: 400
tetapi jika saya akan menghapus-2
itu akan berubahused: 470
jadi sekitar 50MB hilang, dan 50 * 2 lama pasti jauh lebih sedikit dari 50MB[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
Ini membuktikan bahwa dua lama terakhir memaksa G1 untuk mengalokasikan wilayah 1MB lainnya hanya untuk menyimpan 16 byte.