Java Flight Recorder (JFR) adalah profiler built-in OpenJDK yang dirancang untuk berjalan terus-menerus di produksi dengan overhead sangat rendah. JDK Mission Control (JMC) adalah klien GUI yang membaca file rekaman JFR dan menampilkannya dalam bentuk grafik, tabel, dan flamegraph. Pada chapter ini kita akan membahas arsitektur JFR, cara memulai dan menghentikan rekaman, event-event yang terekam, serta workflow analisis dengan JMC.
Mengapa JFR Berbeda #
Berbeda dengan profiler tradisional yang memerlukan instrumentation atau sampling dengan overhead 5-30%, JFR didesain dari awal untuk continuous profiling dengan overhead di bawah 1%. Hal ini dimungkinkan oleh tiga desain:
- Event-based, bukan sampling periodik. JFR merekam event spesifik (alokasi objek besar, GC, monitor wait, method compile) dengan biaya konstan per event.
- Buffer lock-free: setiap thread memiliki buffer lokal, menulis tanpa sinkronisasi.
- Threshold filter: event di bawah ambang batas (mis. durasi < 10 ms) dibuang sebelum ditulis.
Memulai Rekaman JFR #
JFR dikendalikan via flag -XX:StartFlightRecording saat startup atau jcmd saat runtime:
# 1. Rekaman selama 60 detik saat startup
java -XX:StartFlightRecording=duration=60s,filename=/tmp/recording.jfr \
-jar aplikasi.jar
# 2. Rekaman dengan kustomisasi (settings profile)
java -XX:StartFlightRecording=filename=/tmp/rec.jfr,settings=profile,delay=30s \
-jar aplikasi.jar
# 3. Memulai rekaman saat aplikasi sudah berjalan
jcmd <PID> JFR.start name=profil-1 duration=300s filename=/tmp/rec.jfr
# 4. Dump rekaman yang sedang berjalan ke file tanpa menghentikan
jcmd <PID> JFR.dump name=profil-1 filename=/tmp/dump.jfr
# 5. Menghentikan rekaman lebih awal
jcmd <PID> JFR.stop name=profil-1Event yang Terekam #
JFR mengkategorikan event menjadi beberapa grup:
| Grup | Contoh event | Kegunaan |
|---|---|---|
jdk.GCPhasePause | GC pause, allocation | Analisis memory |
jdk.SocketRead/Write | I/O jaringan | Latensi jaringan |
jdk.FileRead/Write | I/O file | Bottleneck disk |
jdk.LockMonitorWait | Lock contention | Thread contention |
jdk.ExecutionSample | CPU sample | Hot path CPU |
jdk.ObjectAllocationSample | Alokasi objek | Hot path allocation |
jdk.ThreadPark | Thread park/wait | Idle/lock |
jdk.JavaExceptionThrow | Exception lemparan | Exception storm |
JDK Mission Control #
JMC adalah GUI untuk membaca file .jfr. Setelah file dibuka, JMC menampilkan beberapa tab:
- General: ringkasan durasi, jumlah event, JVM args
- JVM Internal: class loading, compilation, GC
- Memory: heap usage, allocation rate per type
- Lock instances: monitor contention
- Method Profiling: top methods by CPU sample, flamegraph
- I/O: read/write latency per file/socket
Tab Method Profiling dan Memory paling sering dipakai untuk investigasi performa. JMC juga mendukung plugin custom event type via Event Streaming API sejak JDK 14.
Demonstrasi Beban Kerja #
Kode berikut menghasilkan beban kerja yang akan terekam jelas di JFR. Tiga fase yang berbeda (alokasi, CPU, I/O) membuat hasil rekaman mudah dianalisis.
data class Tugas(val id: Int, val judul: String, val selesai: Boolean)
fun main() {
println("=== Simulasi Beban Kerja untuk JFR ===")
// Phase 1: alokasi singkat (visible di allocation profile)
val tugasList = (1..5).map { i ->
Tugas(id = i, judul = "Tugas-$i", selesai = false)
}
println("Fase 1: membuat ${tugasList.size} objek Tugas")
tugasList.take(2).forEach { println(" - $it") }
// Phase 2: operasi CPU singkat (visible di CPU profile)
println()
println("Fase 2: hitung jumlah bilangan prima < 20")
val prima = (2..19).filter { apakahPrima(it) }
println(" Bilangan prima: $prima")
println(" Jumlah: ${prima.size}")
// Phase 3: I/O simulasi (visible di wall-clock profile)
println()
println("Fase 3: simulasi I/O singkat")
Thread.sleep(10)
println(" I/O selesai setelah 10 ms sleep")
println()
println("=== Yang Terekam oleh JFR ===")
println("- Allocation: Tugas instances, lambda map/filter")
println("- CPU : apakahPrima() calls")
println("- Wall : Thread.sleep() di Fase 3")
println("- GC : minor GC saat heap terisi")
}
fun apakahPrima(n: Int): Boolean {
if (n < 2) return false
var i = 2
while (i * i <= n) {
if (n % i == 0) return false
i++
}
return true
}Output yang Dihasilkan:
=== Simulasi Beban Kerja untuk JFR ===
Fase 1: membuat 5 objek Tugas
- Tugas(id=1, judul=Tugas-1, selesai=false)
- Tugas(id=2, judul=Tugas-2, selesai=false)
Fase 2: hitung jumlah bilangan prima < 20
Bilangan prima: [2, 3, 5, 7, 11, 13, 17, 19]
Jumlah: 8
Fase 3: simulasi I/O singkat
I/O selesai setelah 10 ms sleep
=== Yang Terekam oleh JFR ===
- Allocation: Tugas instances, lambda map/filter
- CPU : apakahPrima() calls
- Wall : Thread.sleep() di Fase 3
- GC : minor GC saat heap terisiMari kita telaah kode di atas:
- Fase 1 (alokasi):
maplambda menghasilkanTugasinstance. Eventjdk.ObjectAllocationSampleakan menangkap tipe ini dengan stack trace yang menunjuk kemainlambda. - Fase 2 (CPU):
apakahPrimadipanggil 18 kali. Eventjdk.ExecutionSampleakan menangkap method ini sebagai hot path CPU bila dijalankan lebih lama. - Fase 3 (I/O wait):
Thread.sleep(10)membuat thread masuk statusTIMED_WAITING. Eventjdk.ThreadParkakan tercatat. - Ringkasan JFR: bagian akhir mencetak tipe event yang diharapkan muncul. Pembaca dapat membandingkan harapan dengan output JMC setelah menjalankan rekaman.
Continuous Profiling #
Untuk aplikasi produksi, JFR sering dipakai dengan durasi panjang dan rotasi file. Pendekatan ini disebut continuous profiling:
# 30 menit per file, rotasi otomatis
java -XX:StartFlightRecording=maxsize=100M,maxage=30m,filename=/var/log/jfr/app.jfr \
-jar aplikasi.jarFile .jfr lama akan otomatis di-rotate bila ukuran atau umur melewati batas. Untuk analisis insiden, ambil file .jfr yang mencakup waktu insiden dan buka di JMC.
Referensi Lanjutan #
- OpenJDK jdk.jfr source
— kode sumber modul
jdk.jfrOpenJDK untuk JDK Flight Recorder - JVM Guide — panduan HotSpot JVM Java SE 21 untuk integrasi JFR
Penutup #
JFR + JMC memberi kombinasi continuous profiling yang praktis untuk produksi. Overhead rendah memungkinkan JFR selalu aktif sehingga kita memiliki data historis saat insiden terjadi. Pada chapter terakhir section ini, kita akan membahas pola-pola performa memori yang umum dan cara mengoptimasi alokasi objek.