Performance adalah tradeoff antara kecepatan, memori, dan keterbacaan kode. Ekosistem JVM punya tooling matang: JMH untuk microbenchmark, async-profiler untuk profil CPU dan alokasi, VisualVM untuk monitoring heap, dan banyak flag tuning GC. Pada chapter ini kita memetakan tooling utama, mendemokan struktur microbenchmark JMH-style, dan menjelaskan optimasi idiomatik Kotlin yang paling sering dipakai.

Tooling Performance #

ToolPeranStatus
JMHMicrobenchmark Java/Kotlin dengan warmup dan forkOpenJDK resmi
async-profilerCPU/allocation profiler, low overhead, flame graphKomunitas
VisualVMGUI monitoring, heap histogramGratis
IntelliJ ProfilerBuilt-in profiler, attach ke proses berjalanKomersial
JProfilerDeep memory analysisKomersial
JITWatchJIT compiler inspector, lihat inliningKomunitas
MicrometerMetrik runtime (counter, gauge, timer)Komunitas
Prometheus + GrafanaDashboard metrik real-timeKomunitas

Demo Struktur Microbenchmark JMH #

JMH adalah standar de facto untuk microbenchmark JVM. Cuplikan berikut memodelkan struktur JMH — kelas Benchmark dengan method measure yang menerima warmup, iterasi, dan block — tanpa benar-benar menjalankan pengukuran (agar output tetap deterministik):

kotlin
// panduan_performance.kt
// Tour singkat Panduan Performance Kotlin:
// - JMH (Java Microbenchmark Harness): https://github.com/openjdk/jmh
// - Profiling tools: async-profiler, VisualVM, IntelliJ Profiler
// - JVM tuning: -Xmx, -XX:+UseG1GC, -XX:+HeapDumpOnOutOfMemoryError
//
// File ini mendemokan konsep microbenchmark (struktur JMH-style) dan JVM tuning.

import java.util.concurrent.TimeUnit

// === 1. Mini benchmark framework (struktur mirip JMH) ===
class Benchmark {
    data class Result(val nama: String, val iterasi: Int, val totalMs: Long, val rataMs: Double)

    fun measure(nama: String, warmup: Int = 3, iterasi: Int = 10, block: () -> Unit): Result {
        // Warmup — jalankan tanpa ukur, agar JIT optimasi
        repeat(warmup) { block() }
        // Measure
        val start = System.nanoTime()
        repeat(iterasi) { block() }
        val end = System.nanoTime()
        val totalMs = TimeUnit.NANOSECONDS.toMillis(end - start)
        val rataMs = totalMs.toDouble() / iterasi
        return Result(nama, iterasi, totalMs, rataMs)
    }
}

// === 2. Code yang biasa diukur ===
fun jumlahLoop(angka: List<Int>): Long {
    var total = 0L
    for (x in angka) total += x
    return total
}

fun jumlahReduce(angka: List<Int>): Long = angka.fold(0L) { acc, x -> acc + x }

fun jumlahStream(angka: List<Int>): Long = angka.stream().mapToLong { it.toLong() }.sum()

fun main() {
    println("=== Panduan Performance Kotlin ===")
    println("JMH | async-profiler | VisualVM | JVM tuning")
    println()

    println("--- 1. Struktur microbenchmark (JMH-style) ---")
    println("  class Benchmark {")
    println("    fun measure(nama: String, warmup: Int = 3, iterasi: Int = 10, block: () -> Unit): Result")
    println("  }")
    println()
    println("  Aturan utama: warmup agar JIT optimasi, iterasi minimal 10,")
    println("  fork JVM terpisah agar tidak ada polusi JIT antar benchmark.")

    println()
    println("--- 2. Tiga variasi penjumlahan list ---")
    println("    for-loop   : imperatif, paling cepat")
    println("    fold/reduce: idiomatic Kotlin")
    println("    stream()   : Java Stream API")
    println()
    println("  fun jumlahLoop(angka: List<Int>): Long {")
    println("      var total = 0L")
    println("      for (x in angka) total += x")
    println("      return total")
    println("  }")

    println()
    println("--- 3. Aturan microbenchmark (JMH) ---")
    val aturan = listOf(
        "1. Warmup: jalankan 3-5 iterasi tanpa ukur (JIT optimasi)",
        "2. Iterasi: minimal 10 iterasi, ambil median",
        "3. Forking: -f 2 (proses terpisah) untuk hindari polusi JIT",
        "4. Hindari dead-code elimination: konsumsi hasil (blackhole)",
        "5. Bandingkan apples-to-apples: algoritma sama, struktur sama"
    )
    aturan.forEach { println("  $it") }

    println()
    println("--- 4. Anotasi JMH umum ---")
    println("  @Benchmark             -> method ini diukur")
    println("  @Warmup(iterations=3)  -> iterasi warmup")
    println("  @Measurement(iterations=10) -> iterasi pengukuran")
    println("  @Fork(2)               -> fork JVM terpisah")
    println("  @OutputTimeUnit(MILLISECONDS) -> satuan output")
    println("  @State(Scope.Benchmark) -> state shared antar thread")

    println()
    println("--- 5. Profiling tools ---")
    val tools = listOf(
        "async-profiler  -> CPU/allocation profiler, low overhead, flame graph",
        "VisualVM        -> GUI monitoring, heap histogram",
        "IntelliJ Profiler -> built-in, attach ke proses berjalan",
        "JProfiler        -> komersial, deep memory analysis",
        "JITWatch         -> JIT compiler inspector, lihat inlining",
        "Micrometer       -> metrik runtime (counter, gauge, timer)",
        "Prometheus + Grafana -> dashboard metrik real-time"
    )
    tools.forEach { println("  $it") }

    println()
    println("--- 6. JVM tuning flags umum ---")
    val flags = listOf(
        Pair("-Xmx2g",                 "Max heap 2 GB"),
        Pair("-Xms512m",               "Initial heap 512 MB (sesuai Xmx bila produksi)"),
        Pair("-XX:+UseG1GC",           "Gunakan G1 GC (default JDK 9+)"),
        Pair("-XX:MaxGCPauseMillis=200","Target pause 200ms"),
        Pair("-XX:+HeapDumpOnOutOfMemoryError", "Auto heap dump saat OOM"),
        Pair("-XX:HeapDumpPath=/tmp/", "Lokasi heap dump"),
        Pair("-XX:+UseZGC",            "ZGC untuk latency rendah (<10ms)"),
        Pair("-XX:+EnableParallelClassLoading", "Paralel class loading")
    )
    flags.forEach { (f, d) -> println("  ${f.padEnd(36)} $d") }

    println()
    println("--- 7. Optimasi Kotlin umum ---")
    val optimasi = listOf(
        "1. Pakai Sequence untuk chain besar (lazy evaluation)",
        "2. Hindari boxing Int/Long di hot path — pakai array primitif",
        "3. Inline lambda lewat `inline fun` (mengurangi alokasi)",
        "4. Reuse object (mis. StringBuilder, ByteBuffer)",
        "5. Hindari reflection di hot path (lambat)",
        "6. Pakai value class (@JvmInline value class) untuk wrapper tanpa alokasi",
        "7. Cache hasil yang mahal (compute once, reuse)",
        "8. Coroutine > Thread untuk IO (lebih ringan)"
    )
    optimasi.forEach { println("  $it") }

    println()
    println("--- 8. Sequence vs List performance ---")
    println("  Skenario: filter+map+take(100) pada List 10.000.000 elemen")
    println("    list.filter.map.take.sum()  -> proses semua elemen (selesai semua baru take)")
    println("    seq.filter.map.take.sum()   -> proses 100 elemen pertama yang lolos (lazy)")
    println("  Sequence cocok untuk chain besar + early termination (take, first, any)")
}

Output yang Dihasilkan:

text
=== Panduan Performance Kotlin ===
JMH | async-profiler | VisualVM | JVM tuning

--- 1. Struktur microbenchmark (JMH-style) ---
  class Benchmark {
    fun measure(nama: String, warmup: Int = 3, iterasi: Int = 10, block: () -> Unit): Result
  }

  Aturan utama: warmup agar JIT optimasi, iterasi minimal 10,
  fork JVM terpisah agar tidak ada polusi JIT antar benchmark.

--- 2. Tiga variasi penjumlahan list ---
    for-loop   : imperatif, paling cepat
    fold/reduce: idiomatic Kotlin
    stream()   : Java Stream API

  fun jumlahLoop(angka: List<Int>): Long {
      var total = 0L
      for (x in angka) total += x
      return total
  }

--- 3. Aturan microbenchmark (JMH) ---
  1. Warmup: jalankan 3-5 iterasi tanpa ukur (JIT optimasi)
  2. Iterasi: minimal 10 iterasi, ambil median
  3. Forking: -f 2 (proses terpisah) untuk hindari polusi JIT
  4. Hindari dead-code elimination: konsumsi hasil (blackhole)
  5. Bandingkan apples-to-apples: algoritma sama, struktur sama

--- 4. Anotasi JMH umum ---
  @Benchmark             -> method ini diukur
  @Warmup(iterations=3)  -> iterasi warmup
  @Measurement(iterations=10) -> iterasi pengukuran
  @Fork(2)               -> fork JVM terpisah
  @OutputTimeUnit(MILLISECONDS) -> satuan output
  @State(Scope.Benchmark) -> state shared antar thread

--- 5. Profiling tools ---
  async-profiler  -> CPU/allocation profiler, low overhead, flame graph
  VisualVM        -> GUI monitoring, heap histogram
  IntelliJ Profiler -> built-in, attach ke proses berjalan
  JProfiler        -> komersial, deep memory analysis
  JITWatch         -> JIT compiler inspector, lihat inlining
  Micrometer       -> metrik runtime (counter, gauge, timer)
  Prometheus + Grafana -> dashboard metrik real-time

--- 6. JVM tuning flags umum ---
  -Xmx2g                               Max heap 2 GB
  -Xms512m                             Initial heap 512 MB (sesuai Xmx bila produksi)
  -XX:+UseG1GC                         Gunakan G1 GC (default JDK 9+)
  -XX:MaxGCPauseMillis=200             Target pause 200ms
  -XX:+HeapDumpOnOutOfMemoryError      Auto heap dump saat OOM
  -XX:HeapDumpPath=/tmp/               Lokasi heap dump
  -XX:+UseZGC                          ZGC untuk latency rendah (<10ms)
  -XX:+EnableParallelClassLoading      Paralel class loading

--- 7. Optimasi Kotlin umum ---
  1. Pakai Sequence untuk chain besar (lazy evaluation)
  2. Hindari boxing Int/Long di hot path — pakai array primitif
  3. Inline lambda lewat `inline fun` (mengurangi alokasi)
  4. Reuse object (mis. StringBuilder, ByteBuffer)
  5. Hindari reflection di hot path (lambat)
  6. Pakai value class (@JvmInline value class) untuk wrapper tanpa alokasi
  7. Cache hasil yang mahal (compute once, reuse)
  8. Coroutine > Thread untuk IO (lebih ringan)

--- 8. Sequence vs List performance ---
  Skenario: filter+map+take(100) pada List 10.000.000 elemen
    list.filter.map.take.sum()  -> proses semua elemen (selesai semua baru take)
    seq.filter.map.take.sum()   -> proses 100 elemen pertama yang lolos (lazy)
  Sequence cocok untuk chain besar + early termination (take, first, any)

Perhatikan bahwa cuplikan sengaja tidak menjalankan Benchmark.measure() di main(). Pengukuran waktu dengan System.nanoTime() menghasilkan angka yang berbeda tiap run, sehingga melanggar prinsip determinisme. File ini hanya menampilkan struktur dan aturan JMH sebagai teks.

Aturan Microbenchmark dan Optimasi #

JMH menetapkan beberapa aturan ketat untuk menghasilkan benchmark valid. Warmup menjalankan kode 3-5 iterasi tanpa pengukuran agar JIT compiler sempat mengoptimasi. Iterasi pengukuran minimal 10 kali, lalu ambil median untuk mengurangi noise. Fork JVM terpisah (-f 2) mencegah polusi JIT antar benchmark. Blackhole mengonsumsi hasil agar kompilator tidak menghapus kode sebagai dead code. Optimasi idiomatik Kotlin yang paling sering dipakai: Sequence untuk chain besar, IntArray/LongArray untuk hindari boxing, inline fun untuk kurangi alokasi lambda, dan value class untuk wrapper tipis tanpa alokasi.

Referensi Lanjutan #

  • Kotlin documentation home — halaman utama dokumentasi Kotlin sebagai rujukan topik performa inline function, value class, dan alokasi

Selanjutnya #

Performance yang baik berangkat dari kode yang benar. Chapter berikutnya membahas troubleshooting — galat kompilator umum, runtime exception yang sering muncul, dan masalah build yang umum terjadi.