JVM memberikan portabilitas dan throughput tinggi, tetapi membawa biaya startup 200-2000 ms dan footprint memori 200 MB. Untuk CLI tool, serverless function, dan aplikasi embedded, biaya ini tidak dapat diterima. Kotlin/Native mengatasi masalah tersebut dengan mengompilasi kode Kotlin langsung ke binary native via LLVM — startup 5-50 ms, memori 10-50 MB, dan tidak memerlukan JVM di mesin target. Alternatif lain adalah GraalVM native-image yang mengompilasi bytecode Java/Kotlin menjadi binary via analisis closed-world. Pada chapter ini kita akan membedah target Kotlin/Native, membandingkan dengan GraalVM, serta mendiskusikan trade-off native vs JVM untuk membantu pemilihan strategi distribusi.
Target Kotlin/Native #
Kotlin/Native mendukung banyak target platform. Setiap target menghasilkan binary dalam format OS-spesifik: ELF untuk Linux, Mach-O untuk macOS, PE untuk Windows.
// native_binaries_kotlin_native.kt — Demo konsep Kotlin/Native dan komparasi
fun main() {
println("=== Native Binaries dengan Kotlin/Native ===")
println()
println("Kompilasi Kotlin ke binary native; tanpa JVM di target.")
println()
// === 1. Target Kotlin/Native ===
println("=== 1. Target Kotlin/Native ===")
val targets = listOf(
Triple("linuxX64", "Linux x86_64", "ELF binary 64-bit"),
Triple("linuxArm64", "Linux ARM 64-bit", "Raspberry Pi 4, server ARM"),
Triple("macosX64", "macOS Intel", "Mach-O 64-bit (legacy)"),
Triple("macosArm64", "macOS Apple Silicon", "Mach-O ARM64 (M1/M2)"),
Triple("mingwX64", "Windows x86_64", "PE binary 64-bit (.exe)"),
Triple("iosArm64", "iOS device", "ARM64 binary"),
Triple("iosSimulatorArm64", "iOS simulator M-series", "ARM64 simulator"),
Triple("tvosArm64", "tvOS device", "Set-top box Apple")
)
targets.forEach { (target, platform, binary) ->
println(" " + target.padEnd(22) + " | " + platform.padEnd(28) + " | " + binary)
}
println()
// === 2. Kotlin/Native vs GraalVM native-image ===
println("=== 2. Kotlin/Native vs GraalVM native-image ===")
val comparison = listOf(
Pair("Bahasa input", Pair("Kotlin/Native: Kotlin langsung", "GraalVM: bytecode Java/Kotlin")),
Pair("Compiler", Pair("LLVM backend", "Substrate VM (closed-world analysis")),
Pair("Runtime", Pair("Sendiri (konkuren, GC)", "Java API subset (reflection terbatas)")),
Pair("Binary size", Pair("~5-10 MB", "~50-100 MB (inklusif JDK)")),
Pair("Startup time", Pair("~5-50 ms", "~10-100 ms")),
Pair("Reflection", Pair("Terbatas; @ExportReflection", "Perlu konfigurasi reachability")),
Pair("Cohabit JVM", Pair("Tidak (binary mandiri)", "Bisa (dari JAR)")),
Pair("Best for", Pair("iOS, embedded, CLI", "Serverless, microservice, CLI Java"))
)
comparison.forEach { (aspek, pair) ->
val (native, graal) = pair
println(" " + aspek.padEnd(18) + " | " + native.padEnd(38) + " | " + graal)
}
println()
// === 3. Konfigurasi Gradle Kotlin/Native ===
println("=== 3. Konfigurasi Gradle Kotlin/Native ===")
val gradle = listOf(
"plugins { kotlin(\"multiplatform\") }",
"",
"kotlin {",
" linuxX64(\"native\") {",
" binaries {",
" executable {",
" entryPoint = \"com.example.main\"",
" }",
" }",
" }",
"}"
)
gradle.forEach { println(" " + it) }
println()
// === 4. Perintah build ===
println("=== 4. Perintah Build ===")
val cmds = listOf(
Pair("./gradlew linkReleaseExecutableNative", "Build optimized binary di build/bin/native/releaseExecutable/"),
Pair("./gradlew linkDebugExecutableNative", "Build debug binary (lebih cepat compile)"),
Pair("./gradlew assemble", "Build semua target termasuk native"),
Pair("native-image -jar app.jar app", "GraalVM native-image dari JAR"),
Pair("native-image --no-fallback -jar app.jar app", "GraalVM tanpa fallback ke JVM")
)
cmds.forEach { (cmd, desc) ->
println(" " + cmd.padEnd(45) + " -> " + desc)
}
println()
// === 5. Trade-off native vs JVM ===
println("=== 5. Trade-off Native vs JVM ===")
val trades = listOf(
Triple("Startup time", "Native: 5-50 ms", "JVM: 200-2000 ms (warm-up)"),
Triple("Peak throughput", "Native: lebih rendah (no JIT)", "JVM: lebih tinggi setelah JIT)"),
Triple("Memory usage", "Native: 10-50 MB", "JVM: 200-500 MB (heap default)"),
Triple("Binary size", "Native: 5-10 MB", "JVM: 0 (JAR) + JVM 200 MB"),
Triple("Reflection", "Native: terbatas", "JVM: penuh"),
Triple("Hot reload", "Native: rebuild binary", "JVM: hot swap / agent")
)
trades.forEach { (aspek, nativeRow, jvmRow) ->
println(" " + aspek.padEnd(20) + " | " + nativeRow.padEnd(35) + " | " + jvmRow)
}
println()
// === 6. Use cases ===
println("=== 6. Use Cases ===")
val useCases = listOf(
"Kotlin/Native: iOS app, embedded, CLI tool tanpa JVM",
"GraalVM: AWS Lambda, microservice, CLI Java",
"JVM fat JAR: server app dengan puncak throughput",
"jlink: custom runtime image untuk Docker minimal"
)
useCases.forEach { println(" " + it) }
println()
// === 7. Aturan praktis ===
println("=== 7. Aturan Praktis ===")
val rules = listOf(
"1. CLI tool ringan -> Kotlin/Native atau GraalVM native-image",
"2. Server throughput -> JVM dengan GC tuning",
"3. iOS / Apple platform -> Kotlin/Native (wajib)",
"4. Embedded / IoT -> Kotlin/Native (binary kecil)",
"5. Serverless cold start -> GraalVM native-image",
"6. Reflection ekstensif -> tetap di JVM"
)
rules.forEach { println(" " + it) }
}Output yang Dihasilkan:
=== Native Binaries dengan Kotlin/Native ===
Kompilasi Kotlin ke binary native; tanpa JVM di target.
=== 1. Target Kotlin/Native ===
linuxX64 | Linux x86_64 | ELF binary 64-bit
linuxArm64 | Linux ARM 64-bit | Raspberry Pi 4, server ARM
macosX64 | macOS Intel | Mach-O 64-bit (legacy)
macosArm64 | macOS Apple Silicon | Mach-O ARM64 (M1/M2)
mingwX64 | Windows x86_64 | PE binary 64-bit (.exe)
iosArm64 | iOS device | ARM64 binary
iosSimulatorArm64 | iOS simulator M-series | ARM64 simulator
tvosArm64 | tvOS device | Set-top box Apple
=== 2. Kotlin/Native vs GraalVM native-image ===
Bahasa input | Kotlin/Native: Kotlin langsung | GraalVM: bytecode Java/Kotlin
Compiler | LLVM backend | Substrate VM (closed-world analysis
Runtime | Sendiri (konkuren, GC) | Java API subset (reflection terbatas)
Binary size | ~5-10 MB | ~50-100 MB (inklusif JDK)
Startup time | ~5-50 ms | ~10-100 ms
Reflection | Terbatas; @ExportReflection | Perlu konfigurasi reachability
Cohabit JVM | Tidak (binary mandiri) | Bisa (dari JAR)
Best for | iOS, embedded, CLI | Serverless, microservice, CLI Java
=== 3. Konfigurasi Gradle Kotlin/Native ===
plugins { kotlin("multiplatform") }
kotlin {
linuxX64("native") {
binaries {
executable {
entryPoint = "com.example.main"
}
}
}
}
=== 4. Perintah Build ===
./gradlew linkReleaseExecutableNative -> Build optimized binary di build/bin/native/releaseExecutable/
./gradlew linkDebugExecutableNative -> Build debug binary (lebih cepat compile)
./gradlew assemble -> Build semua target termasuk native
native-image -jar app.jar app -> GraalVM native-image dari JAR
native-image --no-fallback -jar app.jar app -> GraalVM tanpa fallback ke JVM
=== 5. Trade-off Native vs JVM ===
Startup time | Native: 5-50 ms | JVM: 200-2000 ms (warm-up)
Peak throughput | Native: lebih rendah (no JIT) | JVM: lebih tinggi setelah JIT)
Memory usage | Native: 10-50 MB | JVM: 200-500 MB (heap default)
Binary size | Native: 5-10 MB | JVM: 0 (JAR) + JVM 200 MB
Reflection | Native: terbatas | JVM: penuh
Hot reload | Native: rebuild binary | JVM: hot swap / agent
=== 6. Use Cases ===
Kotlin/Native: iOS app, embedded, CLI tool tanpa JVM
GraalVM: AWS Lambda, microservice, CLI Java
JVM fat JAR: server app dengan puncak throughput
jlink: custom runtime image untuk Docker minimal
=== 7. Aturan Praktis ===
1. CLI tool ringan -> Kotlin/Native atau GraalVM native-image
2. Server throughput -> JVM dengan GC tuning
3. iOS / Apple platform -> Kotlin/Native (wajib)
4. Embedded / IoT -> Kotlin/Native (binary kecil)
5. Serverless cold start -> GraalVM native-image
6. Reflection ekstensif -> tetap di JVMMari kita bedah ringkasan di atas:
- Target: Kotlin/Native mendukung 8+ target. Untuk iOS, target
iosArm64(device) daniosSimulatorArm64(simulator M-series) adalah yang paling umum dipakai di KMP. - Perbandingan: Kotlin/Native mengompilasi langsung dari source Kotlin via LLVM; GraalVM mengompilasi bytecode yang sudah ada. Kotlin/Native menghasilkan binary lebih kecil (5-10 MB) karena tidak menyertakan JDK; GraalVM menghasilkan 50-100 MB karena mengemas sebagian JDK.
- Konfigurasi Gradle: plugin
kotlin("multiplatform")mengekspos DSLkotlin { linuxX64("native") { binaries { executable { entryPoint = "..." } } } }.entryPointmenentukan functionmain()mana yang menjadi titik masuk. - Perintah Build:
linkReleaseExecutableNativemenghasilkan binary optimized (mirip-O2di C);linkDebugExecutableNativelebih cepat compile tetapi binary lebih besar dan lebih lambat. - Trade-off: Native menang di startup dan memori; JVM menang di peak throughput karena JIT mengoptimalkan hot path berdasarkan profil runtime. Native tidak punya JIT — performa bytecode identik dari awal hingga akhir.
- Use Cases: Kotlin/Native wajib untuk iOS (Apple tidak mengizinkan JVM). GraalVM populer untuk serverless karena cold start yang cepat.
Konfigurasi Gradle Kotlin/Native #
Untuk proyek Kotlin Multiplatform yang ingin menghasilkan binary native, gunakan plugin kotlin("multiplatform") dan deklarasikan target dengan binaries.executable():
plugins {
kotlin("multiplatform") version "2.0.21"
}
kotlin {
linuxX64("native") {
binaries {
executable {
entryPoint = "com.example.main"
// Optimasi binary
optimization = true
}
}
}
}Build menghasilkan build/bin/native/releaseExecutable/native.kexe (Linux/macOS) atau native.exe (Windows).
GraalVM native-image #
Alternatif untuk distribusi tanpa JVM adalah GraalVM native-image. Tool ini mengompilasi bytecode Java/Kotlin menjadi binary native via closed-world analysis — semua path yang mungkin dieksekusi dianalisis statis. Karena analisis ini, reflection dinamis (mis. Class.forName(namaDariFile)) tidak otomatis terdeteksi dan butuh konfigurasi reachability manual via reflect-config.json.
native-image -jar app.jar app
./app # binary native, startup ~10 msTrade-off utama: binary GraalVM besar (50-100 MB) karena menyertakan subset JDK. Untuk CLI kecil, Kotlin/Native lebih ringkas.
Referensi Lanjutan #
- Kotlin/Native overview — panduan resmi Kotlin/Native untuk menghasilkan binary native
- GraalVM Native Image — dokumentasi GraalVM Native Image sebagai alternatif binary native JVM
Selanjutnya #
Native binary menyelesaikan masalah startup dan footprint, tetapi konfigurasi runtime tetap perlu dikelola terpisah. Bab berikutnya membahas konfigurasi runtime: env vars, system properties, dan file konfigurasi (HOCON, YAML, TOML) — topik pada chapter Runtime Configuration.