Setiap file .class yang dihasilkan kotlinc mengikuti format biner standar JVM Specification. Format ini bukan sekadar kumpulan instruksi mesin; file .class mengemas metadata lengkap tentang class, termasuk constant pool, daftar field, daftar method, dan atribut seperti Line Number Table yang dipakai untuk memetakan bytecode ke baris sumber. Pada chapter ini kita akan membongkar struktur file .class hasil kompilasi kode Kotlin sederhana, membaca instruksi bytecode utama, dan menggunakan javap -c untuk menampilkan isi class dalam bentuk yang dapat dibaca manusia.

Struktur File .class #

Spesifikasi JVM mendefinisikan struktur file .class sebagai urutan field berukuran tetap dan tabel variabel. Field pertama adalah magic number 0xCAFEBABE yang menjadi penanda bahwa berkas memang berformat class. Setelah magic number terdapat version (major dan minor), constant pool count, constant pool entries, access flags, this class, super class, interfaces, fields, methods, dan terakhir attributes.

Komponen yang paling sering kita jumpai saat membaca bytecode adalah:

KomponenPeran
Constant poolTabel string, angka, dan referensi simbol yang dipakai berulang oleh method
Access flagsModifier public, final, abstract, static
FieldsVariabel instance dan static, dengan tipe descriptor
MethodsInstruksi bytecode + atribut Code (max stack, max locals, exception table)
AttributesMetadata tambahan: SourceFile, LineNumberTable, LocalVariableTable

Kode Sumber Sederhana #

Mari kita pakai kode berikut sebagai objek pengamatan. Kode ini sederhana namun mencakup pemanggilan method, string template, dan variabel lokal — tiga pola yang akan terlihat jelas di bytecode.

kotlin
fun main() {
    println("Halo, bytecode!")
    val angka = 42
    println("Angka favorit: $angka")
}

Output yang Dihasilkan:

text
Halo, bytecode!
Angka favorit: 42

Setelah kompilasi dengan kotlinc bytecode_demo.kt -d out/, kita memperoleh satu file Bytecode_demoKt.class (nama di-mangle dengan akhiran Kt karena fungsi top-level ditempatkan di kelas synthetic). File inilah yang akan kita periksa dengan javap.

Inspeksi dengan javap #

javap adalah alat bawaan JDK yang mencetak struktur class. Opsi -c menampilkan instruksi bytecode, -p menampilkan anggota privat, dan -v menampilkan seluruh atribut termasuk constant pool.

bash
# Kompilasi tanpa -include-runtime agar hanya menghasilkan .class
kotlinc bytecode_demo.kt -d out/
# Lihat metode dan signature saja
javap out/Bytecode_demoKt.class
# Lihat instruksi bytecode
javap -c -p out/Bytecode_demoKt.class
# Lihat seluruh atribut termasuk constant pool
javap -v -p out/Bytecode_demoKt.class

Berikut adalah cuplikan hasil javap -c -p pada method main (output disederhanakan untuk kejelasan):

text
public final static main()V
  // string "Halo, bytecode!" dari constant pool #13
  0: getstatic     #13   // Field java/lang/System.out:Ljava/io/PrintStream;
  3: ldc           #15   // String "Halo, bytecode!"
  5: invokevirtual #17   // Method java/io/PrintStream.println
  // val angka = 42
  8: iconst_42
  9: istore_0
  // println("Angka favorit: $angka") - dibangun StringBuilder
 10: new           #21   // class java/lang/StringBuilder
 13: dup
 14: ldc           #23   // String "Angka favorit: "
 16: invokevirtual #25   // StringBuilder.append
 19: iload_0
 20: invokevirtual #29   // StringBuilder.append:(I)
 23: invokevirtual #32   // StringBuilder.toString
 26: invokevirtual #17   // PrintStream.println
 29: return

Mari kita telaah baris-baris bytecode di atas:

  1. getstatic #13: mengambil field statik System.out dari class java/lang/System. Index #13 merujuk pada entri constant pool yang berisi referensi simbolik ke field tersebut.
  2. ldc #15: memuat konstanta string "Halo, bytecode!" dari constant pool ke operand stack. ldc adalah singkatan load constant.
  3. invokevirtual #17: memanggil method println(String) pada objek PrintStream. invokevirtual dipakai untuk method instance non-private.
  4. iconst_42 lalu istore_0: menyimpan nilai literal 42 ke variabel lokal index 0 (variabel angka). Instruksi iconst_<n> tersedia untuk angka -1 sampai 5; untuk 42 sebenarnya compiler memakai bipush 42, namun representasi logikanya sama.
  5. new StringBuilder + dup + append + toString: pola standar compiler untuk membangun string template. Setiap $angka dipanggil sebagai append pada StringBuilder, lalu hasilnya di-println.

Constant Pool #

Constant pool adalah tabel semua literal dan referensi simbol yang dipakai dalam class. Setiap entri memiliki tag (CONSTANT_Utf8, CONSTANT_Integer, CONSTANT_String, CONSTANT_Methodref, dsb.) dan diakses melalui index numerik. Saat class dimuat, JVM resolve entri ini menjadi referensi langsung (direct reference) — proses ini disebut resolution.

Method dan Code Attribute #

Setiap method dalam class memiliki atribut Code yang menyimpan:

  • max_stack: kedalaman maksimum operand stack
  • max_locals: jumlah variabel lokal termasuk parameter
  • code[]: array instruksi bytecode
  • exception_table[]: rentang instruksi yang dilindungi try/catch
  • LineNumberTable: pemetaan instruksi → nomor baris sumber (dipakai oleh stack trace)
  • LocalVariableTable: nama variabel lokal (dipakai oleh debugger)

Untuk function main di atas, max_locals = 1 (hanya variabel angka) dan max_stack = 2 (maksimum tiga nilai hidup bersamaan di stack saat membangun StringBuilder).

Referensi Lanjutan #

  • JVM Specifications — spesifikasi resmi JVM Java SE 21 untuk struktur bytecode dan instruction set
  • Kotlin/JVM — dokumentasi backend Kotlin/JVM yang menjelaskan penerjemahan kode ke bytecode

Penutup #

Memahami struktur .class memberi kita pijakan ketika membaca pesan galat linker, menelusuri masalah reflection, atau menulis custom transformasi bytecode dengan ASM. Pada chapter berikutnya, kita akan naik satu tingkat abstraksi dan melihat bagaimana JVM mengelola memori ketika bytecode dijalankan — mulai dari pemisahan stack dan heap.