Pengumpulan Sampah Verbose di Jawa

1. Ikhtisar

Dalam tutorial ini, kita akan melihat cara mengaktifkan pengumpulan sampah verbose di aplikasi Java . Kami akan mulai dengan memperkenalkan apa itu pengumpulan sampah verbose dan mengapa itu bisa berguna.

Selanjutnya, kita akan melihat beberapa contoh berbeda dan kita akan mempelajari tentang opsi konfigurasi berbeda yang tersedia. Selain itu, kami juga akan fokus pada cara menafsirkan keluaran log verbose kami.

Untuk mempelajari lebih lanjut tentang Pengumpulan Sampah (GC) dan implementasi berbeda yang tersedia, lihat artikel kami di Java Garbage Collectors.

2. Pengantar Singkat tentang Pengumpulan Sampah Verbose

Mengaktifkan pencatatan pengumpulan sampah verbose sering kali diperlukan saat menyetel dan men-debug banyak masalah , terutama masalah memori. Faktanya, beberapa orang akan berpendapat bahwa untuk memantau kesehatan aplikasi kami secara ketat, kami harus selalu memantau kinerja Pengumpulan Sampah JVM.

Seperti yang akan kita lihat, log GC adalah alat yang sangat penting untuk mengungkapkan potensi peningkatan pada konfigurasi heap dan GC aplikasi kita. Untuk setiap GC yang terjadi, log GC memberikan data yang tepat tentang hasil dan durasinya.

Seiring waktu, analisis informasi ini dapat membantu kami lebih memahami perilaku aplikasi kami dan membantu kami menyesuaikan kinerja aplikasi kami. Selain itu, ini dapat membantu mengoptimalkan frekuensi GC dan waktu pengumpulan dengan menentukan ukuran heap terbaik, opsi JVM lainnya, dan algoritme GC alternatif.

2.1. Program Java Sederhana

Kami akan menggunakan program Java langsung untuk mendemonstrasikan cara mengaktifkan dan menafsirkan log GC kami:

public class Application { private static Map stringContainer = new HashMap(); public static void main(String[] args) { System.out.println("Start of program!"); String stringWithPrefix = "stringWithPrefix"; // Load Java Heap with 3 M java.lang.String instances for (int i = 0; i < 3000000; i++) { String newString = stringWithPrefix + i; stringContainer.put(newString, newString); } System.out.println("MAP size: " + stringContainer.size()); // Explicit GC! System.gc(); // Remove 2 M out of 3 M for (int i = 0; i < 2000000; i++) { String newString = stringWithPrefix + i; stringContainer.remove(newString); } System.out.println("MAP size: " + stringContainer.size()); System.out.println("End of program!"); } }

Seperti yang bisa kita lihat pada contoh di atas, program sederhana ini memuat 3 juta instance String ke dalam objek Map . Kami kemudian membuat panggilan eksplisit ke pengumpul sampah menggunakan System.gc () .

Terakhir, kami menghapus 2 juta instance String dari Peta . Kami juga secara eksplisit menggunakan System.out.println untuk membuat interpretasi keluaran lebih mudah.

Di bagian selanjutnya, kita akan melihat cara mengaktifkan logging GC.

3. Mengaktifkan GC Logging "sederhana"

Mari kita mulai dengan menjalankan program kita dan mengaktifkan GC verbose melalui argumen start-up JVM kita:

-XX:+UseSerialGC -Xms1024m -Xmx1024m -verbose:gc

Argumen penting di sini adalah -verbose: gc , yang mengaktifkan pencatatan informasi pengumpulan sampah dalam bentuk yang paling sederhana . Secara default, log GC ditulis ke stdout dan harus mengeluarkan baris untuk setiap GC generasi muda dan setiap GC penuh.

Untuk keperluan contoh kita, kita telah menentukan serial sampah kolektor, implementasi GC paling sederhana, melalui argumen -XX: + UseSerialGC .

Kami juga telah menetapkan ukuran heap minimal dan maksimal 1024mb, tetapi tentu saja ada lebih banyak parameter JVM yang dapat kami sesuaikan.

3.1. Pemahaman Dasar tentang Output Verbose

Sekarang mari kita lihat keluaran dari program sederhana kita:

Start of program! [GC (Allocation Failure) 279616K->146232K(1013632K), 0.3318607 secs] [GC (Allocation Failure) 425848K->295442K(1013632K), 0.4266943 secs] MAP size: 3000000 [Full GC (System.gc()) 434341K->368279K(1013632K), 0.5420611 secs] [GC (Allocation Failure) 647895K->368280K(1013632K), 0.0075449 secs] MAP size: 1000000 End of program!

Pada keluaran di atas, kita sudah dapat melihat banyak informasi berguna tentang apa yang terjadi di dalam JVM.

Pada awalnya, keluaran ini bisa terlihat cukup menakutkan, tetapi sekarang mari kita bahas langkah demi langkah.

Pertama-tama, kita dapat melihat bahwa empat koleksi terjadi, satu GC Penuh dan tiga generasi muda pembersihan.

3.2. Output Verbose Lebih Detail

Mari kita uraikan baris keluaran secara lebih rinci untuk memahami dengan tepat apa yang sedang terjadi:

  1. GC atau GC Penuh - Jenis Pengumpulan Sampah, baik GC atau GC Penuh untuk membedakan pengumpulan sampah kecil atau penuh
  2. (Kegagalan Alokasi) atau (System.gc ()) - Penyebab pengumpulan - Kegagalan Alokasi menunjukkan bahwa tidak ada lagi ruang tersisa di Eden untuk mengalokasikan objek kita
  3. 279616K-> 146232K - Memori heap yang ditempati sebelum dan sesudah GC, masing-masing (dipisahkan oleh panah)
  4. (1013632K) - Kapasitas heap saat ini
  5. 0,3318607 detik - Durasi peristiwa GC dalam hitungan detik

Jadi, jika kita mengambil baris pertama, 279616K-> 146232K (1013632K) berarti GC mengurangi memori heap yang ditempati dari 279616K menjadi 146232K . Kapasitas heap pada saat GC adalah 1013632K , dan GC membutuhkan waktu 0,3318607 detik.

Namun, meskipun format pencatatan GC sederhana dapat berguna, ini memberikan detail terbatas. Misalnya, kami tidak dapat mengetahui apakah GC memindahkan objek apa pun dari generasi muda ke generasi tua atau berapa ukuran total generasi muda sebelum dan sesudah setiap koleksi .

Oleh karena itu, pencatatan GC yang terperinci lebih berguna daripada yang sederhana.

4. Mengaktifkan Pencatatan GC "mendetail"

Untuk mengaktifkan pencatatan GC mendetail, kami menggunakan argumen -XX: + PrintGCDetails . Ini akan memberi kita lebih banyak detail tentang setiap GC, seperti:

  • Ukuran generasi muda dan tua sebelum dan sesudah masing-masing GC
  • Waktu yang dibutuhkan agar GC terjadi di generasi muda dan tua
  • Ukuran objek yang dipromosikan di setiap GC
  • Ringkasan ukuran total heap

Pada contoh berikutnya, kita akan melihat cara menangkap informasi yang lebih detail di log yang menggabungkan -verbose: gc dengan argumen tambahan ini.

Harap diperhatikan bahwa flag -XX: + PrintGCDetails sudah tidak digunakan lagi di Java 9, karena mendukung mekanisme logging terpadu yang baru (lebih lanjut tentang ini nanti). Bagaimanapun, padanan baru dari -XX: + PrintGCDetails adalah opsi -Xlog: gc * .

5. Menginterpretasikan Output Verbose yang “mendetail”

Mari kita jalankan program contoh kita lagi:

-XX:+UseSerialGC -Xms1024m -Xmx1024m -verbose:gc -XX:+PrintGCDetails

Kali ini hasilnya lebih bertele-tele:

Start of program! [GC (Allocation Failure) [DefNew: 279616K->34944K(314560K), 0.3626923 secs] 279616K->146232K(1013632K), 0.3627492 secs] [Times: user=0.33 sys=0.03, real=0.36 secs] [GC (Allocation Failure) [DefNew: 314560K->34943K(314560K), 0.4589079 secs] 425848K->295442K(1013632K), 0.4589526 secs] [Times: user=0.41 sys=0.05, real=0.46 secs] MAP size: 3000000 [Full GC (System.gc()) [Tenured: 260498K->368281K(699072K), 0.5580183 secs] 434341K->368281K(1013632K), [Metaspace: 2624K->2624K(1056768K)], 0.5580738 secs] [Times: user=0.50 sys=0.06, real=0.56 secs] [GC (Allocation Failure) [DefNew: 279616K->0K(314560K), 0.0076722 secs] 647897K->368281K(1013632K), 0.0077169 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] MAP size: 1000000 End of program! Heap def new generation total 314560K, used 100261K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000) eden space 279616K, 35% used [0x00000000c0000000, 0x00000000c61e9370, 0x00000000d1110000) from space 34944K, 0% used [0x00000000d3330000, 0x00000000d3330188, 0x00000000d5550000) to space 34944K, 0% used [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000) tenured generation total 699072K, used 368281K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000) the space 699072K, 52% used [0x00000000d5550000, 0x00000000ebcf65e0, 0x00000000ebcf6600, 0x0000000100000000) Metaspace used 2637K, capacity 4486K, committed 4864K, reserved 1056768K class space used 283K, capacity 386K, committed 512K, reserved 1048576K

Kita harus bisa mengenali semua elemen dari log GC sederhana. Tapi ada beberapa item baru.

Sekarang mari kita pertimbangkan item baru dalam output yang disorot dengan warna biru di bagian selanjutnya:

5.1. Menafsirkan GC Kecil di Generasi Muda

Kami akan mulai dengan menganalisis bagian baru di GC minor:

  • [GC (Allocation Failure) [DefNew: 279616K->34944K(314560K), 0.3626923 secs] 279616K->146232K(1013632K), 0.3627492 secs] [Times: user=0.33 sys=0.03, real=0.36 secs]

As before we'll break the lines down into parts:

  1. DefNew – Name of the garbage collector used. This not so obvious name stands for the single-threaded mark-copy stop-the-world garbage collector and is what is used to clean the Young generation
  2. 279616K->34944K – Usage of the Young generation before and after collection
  3. (314560K) – The total size of the Young generation
  4. 0.3626923 secs – The duration in seconds
  5. [Times: user=0.33 sys=0.03, real=0.36 secs] – Duration of the GC event, measured in different categories

Now let's explain the different categories:

  • user – The total CPU time that was consumed by Garbage Collector
  • sys – The time spent in OS calls or waiting for system events
  • real – This is all elapsed time including time slices used by other processes

Since we're running our example using the Serial Garbage Collector, which always uses just a single thread, real-time is equal to the sum of user and system times.

5.2. Interpreting a Full GC

In this penultimate example, we see that for a major collection (Full GC), which was triggered by our system call, the collector used was Tenured.

The final piece of additional information we see is a breakdown following the same pattern for the Metaspace:

[Metaspace: 2624K->2624K(1056768K)], 0.5580738 secs]

Metaspace is a new memory space introduced in Java 8 and is an area of native memory.

5.3. Java Heap Breakdown Analysis

The final part of the output includes a breakdown of the heap including a memory footprint summary for each part of memory.

We can see that Eden space had a 35% footprint and Tenured had a 52% footprint. A summary for Metadata space and class space is also included.

From the above examples, we can now understand exactly what was happening with memory consumption inside the JVM during the GC events.

6. Adding Date and Time Information

No good log is complete without date and time information.

This extra information can be highly useful when we need to correlate GC log data with data from other sources, or it can simply help facilitate searching.

We can add the following two arguments when we run our application to get date and time information to appear in our logs:

-XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps

Each line now starts with the absolute date and time when it was written followed by a timestamp reflecting the real-time passed in seconds since the JVM started:

2018-12-11T02:55:23.518+0100: 2.601: [GC (Allocation ...

Please note that these tuning flags have been removed in Java 9. The new alternative is:

-Xlog:gc*::time

7. Logging to a File

As we've already seen, by default the GC log is written to stdout. A more practical solution is to specify an output file.

We can do this by using the argument -Xloggc: where file is the absolute path to our output file:

-Xloggc:/path/to/file/gc.log

Similar to other tuning flags, Java 9 deprecated the -Xloggc flag in favor of the new unified logging. To be more specific, now the alternative for logging to a file is:

-Xlog:gc:/path/to/file/gc.log

8. Java 9: Unified JVM Logging

As of Java 9, most of the GC related tuning flags have been deprecated in favor of the unified logging option -Xlog:gc. The verbose:gc option, however, still works in Java 9 and newer version.

For instance, as of Java 9, the equivalent of the -verbose:gc flag in the new unified logging system is:

-Xlog:gc

This will log all the info level GC logs to the standard output. It's also possible to use the -Xlog:gc= syntax to change the log level. For instance, to see all debug level logs:

-Xlog:gc=debug

As we saw earlier, we can change the output destination via the -Xlog:gc=: syntax. By default, the output is stdout, but we can change it to stderr or even a file:

-Xlog:gc=debug:file=gc.txt

Also, it's possible to add a few more fields to the output using decorators. For instance:

-Xlog:gc=debug::pid,time,uptime

Here we're printing the process id, uptime, and current timestamp in each log statement.

To see more examples of the Unified JVM Logging, see the JEP 158 standard.

9. A Tool to Analyze GC Logs

It can be time-consuming and quite tedious to analyze GC logs using a text editor. Depending on the JVM version and the GC algorithm that is used, the GC log format could differ.

Ada alat analisis grafis gratis yang sangat bagus yang menganalisis catatan pengumpulan Sampah, menyediakan banyak metrik tentang potensi masalah Pengumpulan Sampah, dan bahkan memberikan solusi potensial untuk masalah ini.

Periksa Universal GC Log Analyzer!

10. Kesimpulan

Untuk meringkas, dalam tutorial ini, kita telah menjelajahi pengumpulan sampah verbose secara detail di Java.

Pertama, kami mulai dengan memperkenalkan apa itu pengumpulan sampah verbose dan mengapa kami ingin menggunakannya. Kami kemudian melihat beberapa contoh menggunakan aplikasi Java sederhana. Kami mulai dengan mengaktifkan logging GC dalam bentuknya yang paling sederhana sebelum menjelajahi beberapa contoh yang lebih detail dan cara menafsirkan keluaran.

Akhirnya, kami menjelajahi beberapa opsi tambahan untuk mencatat waktu dan informasi tanggal dan cara menulis informasi ke file log.

Contoh kode dapat ditemukan di GitHub.