Alat-alat sebelumnya memotret kondisi JVM pada satu momen. Namun insiden produksi jarang menunggu fotografer datang: jeda terjadi tiga detik pukul dua pagi, dan yang dibutuhkan adalah rekaman kejadian — bukan potret setelahnya. Java Flight Recorder (JFR) menjawab kebutuhan itu dengan merekam puluhan ribu event per detik — alokasi objek, contoh eksekusi thread, jeda GC, kunci yang diperebutkan — ke buffer melingkar dengan overhead yang dirancang di bawah 1%. Perekaman bisa menyala terus-menerus sejak aplikasi start, dan bila masalah muncul, data tinggal diunduh untuk dianalisis di JDK Mission Control (JMC). Pada chapter ini kita merekam dengan jcmd, membaca rekaman lewat CLI jfr, lalu membuat event kustom milik aplikasi sendiri.
Perekam Penerbangan di Dalam JVM #
Nama Flight Recorder bukan kebetulan — konsepnya diadaptasi dari black box pesawat: alat perekam yang selalu menyala, menyimpan data dalam jendela waktu berputar, dan dibaca hanya saat ada kejadian yang perlu diselidiki. JFR di JVM bekerja dengan dua profil. Perekaman continuous bawaan merekam event-event murah dengan konfigurasi default — layak dinyalakan permanen di produksi. Perekaman profile menambah event yang lebih mahal (sampling eksekusi lebih rapat, alokasi per objek besar) untuk durasi terbatas saat penyelidikan berlangsung.
Mekanisme kuncinya adalah ring buffer per thread: event ditulis ke memori lokal thread tanpa kunci, thread latar belakang yang memindahkannya ke berkas. Overhead rendah itu pula yang membuat JFR menggantikan profil-profiler lawas yang menghentikan seluruh thread untuk pengambilan sampel. Sejak JDK 11, JFR dan JMC bersifat open source dan hadir di seluruh distribusi — tidak lagi fitur berbayar Oracle.
Merekam dengan jcmd #
Perekaman dimulai tanpa menyentuh aplikasi: satu perintah jcmd pada PID target, sebagaimana diperlihatkan percobaan sungguhan berikut pada program uji yang sedang berjalan:
jcmd 14618 JFR.start name="RekamDemo" settings=profile duration=10s filename=/tmp/rekam.jfr14618:
Started recording 1. The result will be written to:
/tmp/rekam.jfrPerintah JFR.start menerima parameter yang membentuk rekaman: name memberi nama agar mudah dirujuk, duration mengakhiri perekaman otomatis (tanpanya rekaman berjalan tanpa batas), settings=profile menaikkan kerincian event, dan filename menentukan berkas keluaran — PID 14618 dan nomor rekaman tentu berbeda di setiap mesin. Keluarga perintah lain melengkapi siklusnya: JFR.dump menuliskan isi buffer ke berkas tanpa menghentikan perekaman, JFR.stop menghentikan, JFR.check mendaftar rekaman aktif. Aplikasi juga bisa memulai dari dalam kode lewat API Recording di module jdk.jfr.
Membaca Rekaman dari CLI #
Berkas .jfr adalah format biner ringkas; alat jfr di folder bin membukanya tanpa GUI. Ringkasan isi rekaman terlihat lewat jfr summary:
jfr summary /tmp/rekam.jfr Version: 2.1
Chunks: 1
Start: 2026-10-15 21:14:18 (UTC)
Duration: 10 s
Event Type Count Size (bytes)
=============================================================
jdk.ModuleExport 508 5434
jdk.BooleanFlag 496 15213
jdk.ActiveSetting 376 9700
jdk.ModuleRequire 156 1560Tabel Event Type merinci jenis event beserta jumlahnya — waktu mulai dan hitungan event pasti berbeda antar rekaman, tetapi jenis-jenisnya konsisten. Isi satu event terbaca utuh dengan jfr print; berikut satu event jdk.ExecutionSample dari rekaman yang sama, yang memotret stack trace thread pada momen sampling:
jdk.ExecutionSample {
startTime = 21:14:18.523 (2026-10-15)
sampledThread = "Attach Listener" (javaThreadId = 21)
state = "STATE_RUNNABLE"
stackTrace = [
jdk.jfr.internal.event.EventWriter.putUncheckedByte(byte) line: 371
jdk.jfr.internal.event.EventWriter.putLong(long) line: 121
...
]
}Dari satu event semacam ini, JMC menyusun flame graph: ribuan ExecutionSample dari thread “utama aplikasi” yang menumpuk di method yang sama berarti method itu memakan CPU paling banyak. Event lain menceritakan babak berbeda — jdk.GarbageCollection merekam durasi tiap koleksi, jdk.JavaMonitorEnter mengukur waktu tunggu kunci, jdk.ObjectAllocationSample menunjuk titik alokasi objek terbesar. Membaca jenis-jenis event ini adalah keterampilan inti analisis performa modern di JVM.
Event Kustom Milik Aplikasi #
JFR bukan monopoli JVM — aplikasi bisa mendefinisikan event sendiri dengan anotasi dari module jdk.jfr, dan event itu akan terekam berdampingan dengan event bawaan. Berikut event kustom untuk alur pengiriman pesan, lalu program yang mengisinya:
import jdk.jfr.Event;
import jdk.jfr.Name;
import jdk.jfr.Description;
@Name("contoh.PesanDikirim")
@Description("Contoh event kustom untuk pembelajaran JFR")
public class EventPesan extends Event {
String penerima;
int ukuran;
}public class KirimPesan {
public static void main(String[] args) {
EventPesan event = new EventPesan();
event.penerima = "Budi";
event.ukuran = 128;
event.begin();
// ... simulasi pekerjaan kirim pesan ...
event.commit();
System.out.println("event terkirim ke perekaman JFR: contoh.PesanDikirim");
System.out.println("event terjadi: " + event.penerima + " ukuran " + event.ukuran);
}
}Output yang Dihasilkan:
event terkirim ke perekaman JFR: contoh.PesanDikirim
event terjadi: Budi ukuran 128Mari kita bedah anatomi event kustomnya:
- Baris 6 (
extends Event): semua event JFR mewarisijdk.jfr.Event— infrastruktur pencatatan waktu dan penulisan buffer datang dari induknya. - Baris 7 (
@Name("contoh.PesanDikirim")): anotasi@Namemenetapkan nama event di berkas rekaman — polanamespace.namamenghindari bentrok dengan event lain. Tanpa anotasi ini, nama event mengikuti nama class. - Baris 10-11 (
penerima,ukuran): field publik class menjadi payload event — persis data yang nanti terbaca dijfr printdan di tabel JMC. - Baris 12-13 di
KirimPesan(begin()lalucommit()):begin()menandai awal durasi,commit()menutup event dan menuliskannya ke perekaman yang menyala. Event tanpabegin()dianggap berdurasi nol — cukup untuk fakta sesaat.
Saat program ini dijalankan biasa, event menguap begitu saja — tidak ada perekaman aktif. Jalankan dengan perekaman menyala (java -XX:StartFlightRecording=filename=rekam.jfr KirimPesan.java), lalu jfr print --events contoh.PesanDikirim rekam.jfr, dan event milik kita muncul di antara ribuan event bawaan — tercatat lengkap dengan penerima = "Budi" dan ukuran = 128.
JMC untuk Analisis #
JDK Mission Control adalah GUI penganalisis berkas .jfr: membuka rekaman menampilkan halaman-halaman siap pakai — Method Profiling dengan flame graph CPU, Garbage Collection dengan deret waktu jeda, Lock Instances untuk perebutan monitor, hingga tampilan Environment berisi bendera JVM dan sistem. Tombol Automated Analysis menjalankan pembacaan aturan: JMC membandingkan angka rekaman dengan ambang praktik sehat lalu menyusun daftar temuan berperingkat — “rata-rata jeda GC melampaui target”, “object allocation rate tinggi di titik X” — lengkap dengan tautan detail dan saran. JMC terpasang terpisah dari JDK, namun format berkasnya kompatibel dengan jfr CLI yang kita pakai di seluruh chapter ini; rekaman yang sama bisa dibaca cepat di terminal dan dibedah nyaman di GUI.
Perangkat perekaman dan pembacanya kini lengkap. Dua chapter terakhir Section ini menuntaskan kotak alat diagnosis: jcmd, jstack, dan jmap untuk menggali kondisi JVM hidup dari command line, lalu kerangka memory tuning yang menyatukan seluruh materi menjadi alur kerja penyetelan.
Referensi #
- Oracle: JFR Tool Reference (JDK 25)
- Oracle: API jdk.jfr (Java SE 25)
- Oracle: jcmd Tool Reference (JDK 25) — JFR.start
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
EventPesan.java
Unduh berkasimport jdk.jfr.Event;
import jdk.jfr.Name;
import jdk.jfr.Description;
@Name("contoh.PesanDikirim")
@Description("Contoh event kustom untuk pembelajaran JFR")
public class EventPesan extends Event {
String penerima;
int ukuran;
}
KirimPesan.java
Unduh berkaspublic class KirimPesan {
public static void main(String[] args) {
EventPesan event = new EventPesan();
event.penerima = "Budi";
event.ukuran = 128;
event.begin();
// ... simulasi pekerjaan kirim pesan ...
event.commit();
System.out.println("event terkirim ke perekaman JFR: contoh.PesanDikirim");
System.out.println("event terjadi: " + event.penerima + " ukuran " + event.ukuran);
}
}