Saat aplikasi Kotlin/JVM berjalan lebih lambat dari yang diharapkan atau mengonsumsi memori lebih besar dari prediksi, kita memerlukan visibilitas ke dalam runtime. Profiling adalah kegiatan mengukur aspek-aspek tersebut — waktu eksekusi method, alokasi objek, dan jejak CPU. Tanpa profiler, optimasi yang kita lakukan sering kali bersifat spekulatif. Pada chapter ini kita akan mengenal alat profiling utama untuk JVM, perbedaan sampler vs profiler, serta cara memilih jenis pengukuran yang tepat.
Kategori Profiler #
Profiler JVM umumnya bekerja dengan dua teknik pengambilan data: instrumentation dan sampling.
| Teknik | Cara kerja | Kelebihan | Kekurangan |
|---|---|---|---|
| Instrumentation | Memasang hook ke setiap method (via bytecode rewriting) | Akurat per-call | Overhead besar, mengubah profil |
| Sampling | Mengambil snapshot stack thread periodik (mis. 10 ms) | Overhead rendah, tidak mengubah profil | Kurang akurat untuk method cepat |
Untuk pekerjaan produksi, sampling hampir selalu menjadi pilihan. Instrumentasi dipakai saat kita perlu akurasi absolut pada method tertentu (mis. unit test microbenchmark).
Alat Utama #
Empat alat berikut paling sering dipakai pada ekosistem JVM:
- VisualVM: GUI gratis dari Oracle (sekarang komunitas). Menggabungkan CPU sampler, memory profiler, heap dump viewer, dan thread monitor. Cocok untuk pengembangan.
- async-profiler: profiler low-overhead berbasis
perf_eventsLinux. Menghasilkan flamegraph dalam format SVG. Tidak mengubah profil karena tidak memakai instrumentation. - Java Flight Recorder (JFR): profiler built-in di OpenJDK 11+. Overhead sangat rendah (< 1% pada beban normal). Cocok untuk continuous profiling.
- IntelliJ Profiler: integrasi async-profiler dan JFR di dalam IDE. Memungkinkan profil langsung dari run configuration.
VisualVM #
VisualVM dapat dipasang secara terpisah atau dipakai sebagai plugin IntelliJ. Setelah aplikasi target berjalan, VisualVM menampilkan proses JVM lokal dan remote. Tab utama:
Overview -> info versi, JVM args, system properties
Monitor -> grafik CPU, heap, threads, classes real-time
Threads -> status setiap thread (runnable, waiting, blocked)
Sampler -> CPU sample dan memory sample
Profiler -> instrumentation mode (memerlukan restart target)Untuk memeriksa alokasi, pakai tab Sampler → Memory → Take snapshot. Snapshot dapat disimpan sebagai .hprof dan dianalisis kemudian.
async-profiler #
async-profiler berjalan sebagai agen native via -agentpath. Output utama adalah flamegraph yang menampilkan tumpukan pemanggilan dengan lebar proporsional terhadap waktu.
# Memasang agen dan menghasilkan flamegraph SVG
java -agentpath:/opt/async-profiler/libasyncProfiler.so=start,event=cpu,file=/tmp/profile.svg \
-jar aplikasi.jar
# Mengubah event menjadi alokasi
java -agentpath:/opt/async-profiler/libasyncProfiler.so=start,event=alloc,file=/tmp/alloc.svg \
-jar aplikasi.jar
# Event lain yang didukung: cache-misses, lock, page-faults, context-switchesJava Flight Recorder #
JFR adalah bagian dari OpenJDK sejak versi 11. Profil disimpan dalam format biner .jfr yang dapat dianalisis dengan JDK Mission Control (JMC) atau di-parse programatik.
# Memulai JFR selama 60 detik
java -XX:StartFlightRecording=duration=60s,filename=/tmp/recording.jfr \
-jar aplikasi.jar
# Atau memulai JFR saat aplikasi sudah berjalan
jcmd <PID> JFR.start duration=60s filename=/tmp/recording.jfr
# Menghentikan recording lebih awal
jcmd <PID> JFR.stop name=1Demonstrasi Beban Kerja #
Kode berikut menghasilkan beban alokasi yang nyata. Saat diprofil, kode ini akan memperlihatkan pola yang khas: alokasi singkat di young generation diikuti pengumpulan oleh minor GC.
data class Pengguna(val id: Int, val nama: String, val email: String)
fun main() {
println("=== Skenario Beban Alokasi Objek ===")
// Membuat banyak objek pengguna untuk simulasi workload
val daftar = mutableListOf<Pengguna>()
repeat(10) { i ->
daftar.add(Pengguna(id = i, nama = "Pengguna-$i", email = "pengguna-$i@example.com"))
}
println("Membuat ${daftar.size} objek Pengguna")
println("Sample objek pertama: ${daftar.first()}")
println("Sample objek terakhir: ${daftar.last()}")
// Menghitung total alokasi memori (estimasi kasar)
println()
println("=== Estimasi Alokasi ===")
println("Setiap Pengguna ≈ 40 byte (header) + 4 byte (Int) + 2 ref (8 byte) = ~60 byte")
println("Total 10 objek ≈ 600 byte + String literals di constant pool")
// Profiling pattern: cek seberapa cepat GC bekerja
println()
println("=== Pola Yang Bagus untuk Profiling ===")
println("1. Hot path: alokasi massal pada loop singkat")
println("2. Steady state: jumlah objek stabil selama waktu")
println("3. Pressure: alokasi terus tumbuh tanpa batas (indikasi leak)")
println()
println("=== Alat Profiling yang Direkomendasikan ===")
println("- VisualVM : GUI gratis, sampler CPU + memory")
println("- async-profiler : low-overhead, flamegraph")
println("- Java Flight Recorder: low-overhead, continuous")
println("- IntelliJ Profiler : integrasi IDE")
}Output yang Dihasilkan:
=== Skenario Beban Alokasi Objek ===
Membuat 10 objek Pengguna
Sample objek pertama: Pengguna(id=0, nama=Pengguna-0, [email protected])
Sample objek terakhir: Pengguna(id=9, nama=Pengguna-9, [email protected])
=== Estimasi Alokasi ===
Setiap Pengguna ≈ 40 byte (header) + 4 byte (Int) + 2 ref (8 byte) = ~60 byte
Total 10 objek ≈ 600 byte + String literals di constant pool
=== Pola Yang Bagus untuk Profiling ===
1. Hot path: alokasi massal pada loop singkat
2. Steady state: jumlah objek stabil selama waktu
3. Pressure: alokasi terus tumbuh tanpa batas (indikasi leak)
=== Alat Profiling yang Direkomendasikan ===
- VisualVM : GUI gratis, sampler CPU + memory
- async-profiler : low-overhead, flamegraph
- Java Flight Recorder: low-overhead, continuous
- IntelliJ Profiler : integrasi IDEMari kita telaah kode di atas:
data class Pengguna: deklarasi ringkas yang menghasilkan class dengan field, constructor,equals,hashCode,toString, dancopy. Inilah pola class yang paling sering muncul di flamegraph alokasi.repeat(10): loop singkat yang mengalokasikan 10 instance. Pada skala produksi, loop 10.000 atau 100.000 iterasi akan terlihat jelas di flamegraph sebagai sumber alokasi utama.- Estimasi alokasi: angka ~60 byte per objek adalah perkiraan untuk class dengan satu
Intdan duaString. Angka sebenarnya tergantung pada compressed oops dan ukuran header objek. - Tiga pola profil: pembaca diarahkan untuk memilih jenis pengukuran (CPU/memory/allocation) berdasarkan pola gejala yang dihadapi.
Memilih Mode Profiling #
| Gejala | Mode profiling yang sesuai |
|---|---|
| Throughput rendah, CPU 100% | CPU sample (async-profiler event=cpu) |
| Latensi tinggi pada beberapa request | CPU sample + wall-clock (event=wall) |
| GC sering, heap cepat penuh | Allocation profile (event=alloc) |
| Lock contention, thread waiting | Lock profile (event=lock) |
| Memory leak bertumbuh pelan | Heap dump + retained size analysis |
Referensi Lanjutan #
- JetBrains Profiler — panduan IntelliJ Profiler untuk CPU, memori, dan thread profiling
- JVM Guide — panduan HotSpot JVM Java SE 21 sebagai dasar observasi runtime
Penutup #
Pemilihan alat profiling yang tepat menentukan apakah kita menginvestigasi masalah dalam hitungan menit atau berhari-hari. VisualVM cocok untuk pengembangan, async-profiler untuk flamegraph produksi, dan JFR untuk continuous profiling. Pada chapter berikutnya kita akan membahas JFR lebih dalam beserta alat analisisnya, JDK Mission Control.