Jika tidak menelepon System.gc()
, sistem akan mengeluarkan OutOfMemoryException. Saya tidak tahu mengapa saya harus menelepon System.gc()
secara eksplisit; JVM harus memanggil gc()
dirinya sendiri, bukan? Mohon saran.
Berikut ini adalah kode pengujian saya:
public static void main(String[] args) throws InterruptedException {
WeakHashMap<String, int[]> hm = new WeakHashMap<>();
int i = 0;
while(true) {
Thread.sleep(1000);
i++;
String key = new String(new Integer(i).toString());
System.out.println(String.format("add new element %d", i));
hm.put(key, new int[1024 * 10000]);
key = null;
//System.gc();
}
}
Sebagai berikut, tambahkan -XX:+PrintGCDetails
untuk mencetak info GC; seperti yang Anda lihat, sebenarnya, JVM mencoba untuk menjalankan GC penuh, tetapi gagal; Saya masih belum tahu alasannya. Sangat aneh bahwa jika saya menghapus tanda komentar pada System.gc();
baris, hasilnya positif:
add new element 1
add new element 2
add new element 3
add new element 4
add new element 5
[GC (Allocation Failure) --[PSYoungGen: 48344K->48344K(59904K)] 168344K->168352K(196608K), 0.0090913 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 48344K->41377K(59904K)] [ParOldGen: 120008K->120002K(136704K)] 168352K->161380K(196608K), [Metaspace: 5382K->5382K(1056768K)], 0.0380767 secs] [Times: user=0.09 sys=0.03, real=0.04 secs]
[GC (Allocation Failure) --[PSYoungGen: 41377K->41377K(59904K)] 161380K->161380K(196608K), 0.0040596 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 41377K->41314K(59904K)] [ParOldGen: 120002K->120002K(136704K)] 161380K->161317K(196608K), [Metaspace: 5382K->5378K(1056768K)], 0.0118884 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at test.DeadLock.main(DeadLock.java:23)
Heap
PSYoungGen total 59904K, used 42866K [0x00000000fbd80000, 0x0000000100000000, 0x0000000100000000)
eden space 51712K, 82% used [0x00000000fbd80000,0x00000000fe75c870,0x00000000ff000000)
from space 8192K, 0% used [0x00000000ff800000,0x00000000ff800000,0x0000000100000000)
to space 8192K, 0% used [0x00000000ff000000,0x00000000ff000000,0x00000000ff800000)
ParOldGen total 136704K, used 120002K [0x00000000f3800000, 0x00000000fbd80000, 0x00000000fbd80000)
object space 136704K, 87% used [0x00000000f3800000,0x00000000fad30b90,0x00000000fbd80000)
Metaspace used 5409K, capacity 5590K, committed 5760K, reserved 1056768K
class space used 576K, capacity 626K, committed 640K, reserved 1048576K
java
java-8
garbage-collection
out-of-memory
weak-references
Dominic Peng
sumber
sumber
Jawaban:
JVM akan memanggil GC sendiri, tetapi dalam hal ini akan terlalu sedikit terlambat. Bukan hanya GC yang bertanggung jawab dalam membersihkan memori dalam kasus ini. Nilai peta sangat terjangkau dan dibersihkan oleh peta itu sendiri ketika operasi tertentu dijalankan di atasnya.
Inilah hasilnya jika Anda menghidupkan acara GC (XX: + PrintGC):
GC tidak terpicu hingga upaya terakhir untuk memberi nilai pada peta.
WeakHashMap tidak dapat menghapus entri basi sampai kunci peta terjadi pada antrian referensi. Dan kunci peta tidak muncul pada antrian referensi sampai mereka dikumpulkan. Alokasi memori untuk nilai peta baru dipicu sebelum peta memiliki peluang untuk membersihkan dirinya sendiri. Ketika alokasi memori gagal dan memicu GC, kunci peta dikumpulkan. Tetapi sudah terlalu sedikit terlambat - tidak cukup memori yang dibebaskan untuk mengalokasikan nilai peta baru. Jika Anda mengurangi payload, Anda mungkin berakhir dengan memori yang cukup untuk mengalokasikan nilai peta baru dan entri basi akan dihapus.
Solusi lain bisa dengan membungkus nilai-nilai itu sendiri ke dalam WeakReference. Ini akan memungkinkan GC menghapus sumber daya tanpa menunggu peta melakukannya sendiri. Inilah hasilnya:
Jauh lebih baik.
sumber
java.util.WeakHashMap.expungeStaleEntries
yang membaca antrian referensi dan menghapus entri dari peta sehingga membuat nilai tidak terjangkau dan tunduk pada koleksi. Hanya setelah itu dilakukan pass kedua GC akan membebaskan memori.expungeStaleEntries
disebut dalam sejumlah kasus seperti get / put / size atau hampir semua yang biasanya Anda lakukan dengan peta. Itu tangkapannya.Jawaban lainnya memang benar, saya mengedit milik saya. Sebagai tambahan kecil,
G1GC
tidak akan menunjukkan perilaku ini, tidak sepertiParallelGC
; yang merupakan default di bawahjava-8
.Menurut Anda apa yang akan terjadi jika saya sedikit mengubah program Anda (berjalan
jdk-8
dengan-Xmx20m
)Ini akan bekerja dengan baik. Mengapa demikian? Karena itu memberi program Anda ruang bernapas yang cukup untuk alokasi baru terjadi, sebelum
WeakHashMap
membersihkan entri. Dan jawaban lain sudah menjelaskan bagaimana itu terjadi.Sekarang, dalam
G1GC
, segalanya akan sedikit berbeda. Ketika seperti benda besar dialokasikan (lebih dari 1/2 MB biasanya ), ini akan disebuthumongous allocation
. Ketika itu terjadi, GC bersamaan akan dipicu. Sebagai bagian dari siklus itu: koleksi muda akan dipicu danCleanup phase
akan diinisiasi yang akan mengurus posting acara keReferenceQueue
, sehinggaWeakHashMap
membersihkan entri.Jadi untuk kode ini:
yang saya jalankan dengan jdk-13 (di mana
G1GC
default)Berikut adalah sebagian dari log:
Ini sudah melakukan sesuatu yang berbeda. Itu dimulai
concurrent cycle
(dilakukan saat aplikasi Anda sedang berjalan), karena ada aG1 Humongous Allocation
. Sebagai bagian dari siklus bersamaan ini, ia melakukan siklus GC muda (yang menghentikan aplikasi Anda saat menjalankan)Sebagai bagian dari GC muda itu, ia juga membersihkan daerah-daerah raksasa , di sini adalah cacatnya .
Anda sekarang dapat melihat bahwa
jdk-13
tidak menunggu sampah menumpuk di wilayah lama ketika benda yang sangat besar dialokasikan, tetapi memicu siklus GC bersamaan , yang menyelamatkan hari; tidak seperti jdk-8.Anda mungkin ingin membaca apa
DisableExplicitGC
dan / atau apaExplicitGCInvokesConcurrent
maksudnya, digabungkan denganSystem.gc
dan memahami mengapa meneleponSystem.gc
sebenarnya membantu di sini.sumber
ParalleGC
, saya telah mengedit dan minta maaf (dan terima kasih) karena membuktikan saya salah.-XX:+UseG1GC
buat itu berfungsi di Java 8, sama seperti-XX:+UseParallelOldGC
membuatnya gagal di JVM baru.