Panduan untuk System.gc ()

1. Ikhtisar

Dalam tutorial ini, kita akan menyelidiki metode System.gc () yang terletak di paket java.lang .

Memanggil System.gc () secara eksplisit dikenal sebagai praktik yang buruk. Mari kita coba untuk memahami mengapa dan jika ada kasus penggunaan saat memanggil metode ini mungkin berguna.

2. Pengumpulan Sampah

Mesin Virtual Java memutuskan untuk melakukan pengumpulan sampah jika ada indikasi untuk melakukannya. Indikasi tersebut berbeda antara implementasi GC yang satu dengan yang lainnya. Mereka didasarkan pada heuristik yang berbeda. Namun, ada beberapa momen dimana GC pasti akan dieksekusi:

  • Generasi baru (Ruang bertempat) penuh, yang memicu GC minor
  • Generasi lama (Eden + Survivor0 + Survivor1 spasi) penuh, yang memicu GC mayor / penuh

Satu-satunya hal yang independen dari penerapan GC adalah kelayakan objek untuk dikumpulkan.

Sekarang, kita akan melihat metode System.gc () itu sendiri.

3. System.gc ()

Pemanggilan metode ini sederhana:

System.gc()

Dokumentasi resmi Oracle menyatakan bahwa:

Memanggil metode gc menunjukkan bahwa Mesin Virtual Java berupaya mendaur ulang objek yang tidak digunakan agar memori yang mereka tempati saat ini tersedia untuk digunakan kembali dengan cepat.

Tidak ada jaminan bahwa GC yang sebenarnya akan terpicu .

System.gc () memicu GC utama. Karenanya, ada risiko menghabiskan waktu pada fase stop-the-world, bergantung pada implementasi pengumpul sampah Anda. Akibatnya, kami memiliki alat yang tidak dapat diandalkan dengan potensi penalti performa yang signifikan .

Keberadaan permintaan pengumpulan sampah eksplisit harus menjadi bendera merah yang serius bagi semua orang.

Kita dapat mencegah System.gc () melakukan pekerjaan apa pun dengan menggunakan tanda JVM -XX: DisableExplicitGC .

3.1. Penyetelan Performa

Perlu dicatat bahwa sebelum melempar OutOfMemoryError, JVM akan melakukan GC penuh. Oleh karena itu, panggilan eksplisit ke System.gc () tidak akan menyelamatkan kita dari kegagalan .

Pengumpul sampah sekarang ini memang pintar banget. Mereka memiliki semua pengetahuan tentang penggunaan memori dan statistik lainnya untuk dapat membuat keputusan yang tepat. Karenanya, kita harus mempercayai mereka.

Dalam kasus masalah memori, kita memiliki banyak pengaturan yang dapat kita ubah untuk menyesuaikan aplikasi kita - mulai dari memilih pengumpul sampah yang berbeda, melalui pengaturan waktu aplikasi / rasio waktu GC yang diinginkan, dan terakhir, diakhiri dengan pengaturan ukuran tetap untuk segmen memori.

Ada juga cara untuk mengurangi efek GC Penuh yang disebabkan oleh panggilan eksplisit. Kita bisa menggunakan salah satu flag:

-XX:+ExplicitGCInvokesConcurrent

atau:

-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses

Jika kita benar-benar ingin aplikasi kita berfungsi dengan baik, kita harus menyelesaikan masalah memori yang mendasarinya.

Di bab berikutnya, kita akan melihat contoh praktis ketika secara eksplisit memanggil System.gc () tampaknya berguna.

4. Contoh Penggunaan

4.1. Skenario

Mari menulis aplikasi uji. Kami ingin menemukan situasi ketika memanggil System.gc () mungkin berguna .

Pengumpulan sampah kecil lebih sering terjadi daripada sampah besar. Jadi, kita mungkin harus fokus pada yang terakhir. Satu objek dipindahkan ke ruang bertenor jika "bertahan" dari beberapa koleksi dan masih dapat dijangkau dari akar GC.

Bayangkan kita memiliki banyak koleksi objek yang hidup selama beberapa waktu. Kemudian, di beberapa titik, kami membersihkan koleksi objek. Mungkin ini saat yang tepat untuk menjalankan System.gc () ?

4.2. Aplikasi Demo

Kami akan membuat aplikasi konsol sederhana yang memungkinkan kami mensimulasikan skenario itu:

public class DemoApplication { private static final Map cache = new HashMap(); public static void main(String[] args) { Scanner scanner = new Scanner(System.in); while (scanner.hasNext()) { final String next = scanner.next(); if ("fill".equals(next)) { for (int i = 0; i < 1000000; i++) { cache.put(randomUUID().toString(), randomUUID().toString()); } } else if ("invalidate".equals(next)) { cache.clear(); } else if ("gc".equals(next)) { System.gc(); } else if ("exit".equals(next)) { System.exit(0); } else { System.out.println("unknown"); } } } }

4.3. Menjalankan Demo

Mari jalankan aplikasi kita dengan beberapa flag tambahan:

-XX:+PrintGCDetails -Xloggc:gclog.log -Xms100M -Xmx500M -XX:+UseConcMarkSweepGC

The first two flags are needed to log GC information. The next two flags are setting initial heap size and then maximum heap size. We want to keep the heap size low to force GC to be more active. Finally, we're deciding to use CMS – Concurrent Mark and Sweep garbage collector. It's time to run our app!

First, let's try to fill tenured space. Type fill.

We can investigate our gclog.log file to see what happened. We'll see around 15 collections. The line logged for single collections looks like:

197.057: [GC (Allocation Failure) 197.057: [ParNew: 67498K->40K(75840K), 0.0016945 secs] 168754K->101295K(244192K), 0.0017865 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] secs]

As we can see, the memory is filled.

Next, let's force System.gc() by typing gc. We can see memory usage didn't change significantly:

238.810: [Full GC (System.gc()) 238.810: [CMS: 101255K->101231K(168352K); 0.2634318 secs] 120693K->101231K(244192K), [Metaspace: 32186K->32186K(1079296K)], 0.2635908 secs] [Times: user=0.27 sys=0.00, real=0.26 secs]

After a few more runs, we'll see that memory size stays at the same level.

Let's clear the cache by typing invalidate. We should see no more log lines appear in the gclog.log file.

We can try to fill cache a few more times, but no GC is happening. This is a moment when we can outsmart the garbage collector. Now, after forcing GC, we'll see a line like:

262.124: [Full GC (System.gc()) 262.124: [CMS: 101523K->14122K(169324K); 0.0975656 secs] 103369K->14122K(245612K), [Metaspace: 32203K->32203K(1079296K)], 0.0977279 secs] [Times: user=0.10 sys=0.00, real=0.10 secs]

We've released an impressive amount of memory! But was it really necessary right now? What happened?

According to this example, calling System.gc() might seem tempting when we're releasing big objects or invalidating caches.

5. Other Usages

There are very few reasons when an explicit call to the System.gc() method might be useful.

One possible reason is cleaning memory after server startup — we're starting a server or application which does a lot of preparation. After that, there are a lot of objects to be finalized. However, cleaning after such preparation shouldn't be our responsibility.

Another is memory leak analysisit's more a debugging practice than something we would like to keep in the production code. Calling System.gc() and seeing heap space still being high might be an indication of a memory leak.

6. Summary

In this article, we investigated the System.gc() method and when it might seem useful.

Kita tidak boleh mengandalkannya dalam hal kebenaran aplikasi kita. GC dalam banyak kasus lebih pintar dari kami, dan jika terjadi masalah memori, kami harus mempertimbangkan untuk menyetel mesin virtual daripada membuat panggilan eksplisit seperti itu.

Seperti biasa, kode yang digunakan dalam artikel ini dapat ditemukan di GitHub.