概要
- バイトコードとは何かをざっくり解説する
- Kotlinのバイトコードを見てみる
バイトコードとは?
- 仮想マシン上で実行される中間コード。仮想マシンがそれぞれのプラットフォームで機械語に変換して実行するので、プラットフォーム依存性がありません。
実際に見てみよう
環境について
- MacOSを使っています
- kotlinc(コンパイラ)はhttps://github.com/JetBrains/kotlin/releases/tag/v2.2.21からダウンロードしています
- 環境はmiseで管理しています
ではやってみよう
最初のディレクトリ構成はこんなかんじです。
. ├── 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で確認できます。
Inuverse Sci. X Tech. Blog