戻る

Kotlinでバイトコードを見てみる

概要

  • バイトコードとは何かをざっくり解説する
  • Kotlinのバイトコードを見てみる

バイトコードとは?

  • 仮想マシン上で実行される中間コード。仮想マシンがそれぞれのプラットフォームで機械語に変換して実行するので、プラットフォーム依存性がありません。

実際に見てみよう

環境について

ではやってみよう

最初のディレクトリ構成はこんなかんじです。

.
├── Main.kt
└── mise.toml

ぶっちゃけ何もありません。

コードは

fun main() {
    println("Hello, Kotlin!")
}

としました。これを次のコマンドでコンパイルします。

kotlinc Main.kt -include-runtime -d main.jar

そして生成されたjarファイルを実行します。

java -jar main.jar

するとHello, Kotlin!という文字列が出力できることを確認できますが、今回着目したいことはこれではありません。 気になるのは生成されるjarファイルです。

次にコマンドで中身を見ることができます。

jar tf main.jar

ここでオプションはそれぞれ

  • t = table(一覧)
  • f = file(対象ファイル指定) を意味します。

実は、jarファイルはzipファイルなので、以下のコマンドでも同じことができます。

unzip -l main.jar
  • -l は「一覧を表示」 を意味します。

実行すると次のような出力が得られます。 .classという謎の拡張子を持つファイルがあるように見えます。

kotlin/reflect/jvm/internal/impl/utils/SmartSet.class
kotlin/reflect/jvm/internal/impl/utils/WrappedValues$1.class
kotlin/reflect/jvm/internal/impl/utils/WrappedValues$ThrowableWrapper.class
kotlin/reflect/jvm/internal/impl/utils/WrappedValues$WrappedProcessCanceledException.class
kotlin/reflect/jvm/internal/impl/utils/WrappedValues.class
kotlin/reflect/jvm/internal/impl/utils/addToStdlib/AddToStdlibKt.class
META-INF/versions/9/kotlin/reflect/jvm/internal/impl/serialization/deserialization/builtins/BuiltInsResourceLoader.class

実際に中身を見るためにinspectディレクトリを作成して、移動し、jarファイルを解凍します。

mkdir inspect
cd inspect
jar xf ../main.jar

実際に中身を見るために、MainKt.classを確認します。

javap -c MainKt.class
  • -c でJVM bytecodeが見える

これの出力は以下のようになります。

Compiled from "Main.kt"
public final class MainKt {
  public static final void main();
    Code:
       0: ldc           #8                  // String Hello, Kotlin!
       2: getstatic     #14                 // Field java/lang/System.out:Ljava/io/PrintStream;
       5: swap
       6: invokevirtual #20                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
       9: return

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #23                 // Method main:()V
       3: return
}

#8や#14などといった謎の数字は参照している定数プールの番号を示しています。 ここで定数プール(constant_pool[])とは、JVMがクラスをロードする際に必要とするすべてのメタデータの「倉庫」です。 定数プールを確認したい時、以下のjavapコマンドで確認できます。

javap -v MainKt.class

実行結果は以下のようになります。

Constant pool:
   #1 = Utf8               MainKt
   #2 = Class              #1             // MainKt
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               main
   #6 = Utf8               ()V
   #7 = Utf8               Hello, Kotlin!
   #8 = String             #7             // Hello, Kotlin!
   #9 = Utf8               java/lang/System
  #10 = Class              #9             // java/lang/System
  #11 = Utf8               out
  #12 = Utf8               Ljava/io/PrintStream;
  #13 = NameAndType        #11:#12        // out:Ljava/io/PrintStream;
  #14 = Fieldref           #10.#13        // java/lang/System.out:Ljava/io/PrintStream;
  ...
  ...
  ...

実は、classファイルはどうやらIDEによって自動でデコンパイラされて

// Source code is decompiled from a .class file using FernFlower decompiler (from Intellij IDEA).
import kotlin.Metadata;

@Metadata(
   mv = {2, 2, 0},
   k = 2,
   xi = 48,
   d1 = {"\u0000\u0006\n\u0000\n\u0002\u0010\u0002\u001a\u0006\u0010\u0000\u001a\u00020\u0001"},
   d2 = {"main", ""}
)
public final class MainKt {
   public static final void main() {
      System.out.println("Hello, Kotlin!");
   }
}

のように見えます。実際の定数プールは前述の通りjavap -v MainKt.classで確認できます。

戻る