Technology of SJI

株式会社SJIの技術ブログ

Build a Java Framework From Scratch(1)

| Comments

本連載はスクラッチで軽量Javaフレームワークの設計、実現方法を解説します。Javaの知識を深めながら、Spring FrameworkのようなAOPxDIフレームワークをゼロから作成してみます。

自力でAOPとDI機能を実現するため、ある程度のJVM知識を習得する必要です。ですから、本題の前にJVM知識を紹介していきたいです。クラスレイアウト定義の解説を始め、JVMランタイム仕組みを紹介し、ASMフレームワークでJava classを操作する方法からAOPとDI機能の実装を展開します。

では、早速クラスレイアウト仕様を解説します。以下の内容はJava仮想マシン仕様SE 7版を参照します。一部用語を英語のまま使用します。

テスト環境

  • OS: Mac OSX 10.8.5
  • Java: Oracle 1.7.0_40(64bit)

Java class format

バイナリJavaクラスファイルは以下の特徴があります。

  • ファイルは8ビット(1バイト)のストリームで構成されます。8ビット以上のデータはBig-Endianの順番で保存します。いわば、高いバイトは低いアドレスに保存されます。(IBMのPowerPCプロセッサはこの順番を採用します。Intelのx86プロセッサは逆順番のLittle-Endianを採用します)。
  • クラスのレイアウトはC言語の構造体のような可変長配列で構成されます。主に2つのデータ・タイプ(符号なし整数とテーブル)があります。
    • u1: 符号なし8ビット整数
    • u2: Big-Endianバイト順の符号なし16ビット整数
    • u4: Big-Endianバイト順の符号なし32ビット整数
    • テーブル: いくつかの型の可変長の配列。テーブルのテーブル内の項目数はカウント数により識別されるが、テーブルのバイト内のサイズは項目それぞれを調査することのみで決定される。

Java仮想マシン仕様に記載されたJavaクラスの構造は以下のようです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ClassFile {
    u4             magic;                                    // マジックナンバー : 0xCAFEBABE
    u2             minor_version;                            // フォーマットのマイナーバージョン
    u2             major_version;                            // フォーマットのメジャーバージョン
    u2             constant_pool_count;                      // 定数プール数
    cp_info        constant_pool[constant_pool_count-1];     // 定数プール情報配列
    u2             access_flags;                             // アクセスフラグ : 例えばクラスがpublicかabstractかなど
    u2             this_class;                               // thisクラス
    u2             super_class;                              // 親クラス
    u2             interfaces_count;                         // インタフェース数
    u2             interfaces[interfaces_count];             // インタフェースの情報配列
    u2             fields_count;                             // クラスまたインスタンス変数の個数
    field_info     fields[fields_count];                     // クラスまたインスタンス変数情報配列
    u2             methods_count;                            // メソッド数(親クラスからのメソッドが含まない)
    method_info    methods[methods_count];                   // メソッドの情報配列
    u2             attributes_count;                         // クラス内の任意属性の数量
    attribute_info attributes[attributes_count];             // クラス内の任意属性の情報配列(例えばソースファイル名、行番号など)
}

コメントを見ればわかりますが、少し説明します。constant_pool[constant_pool_count-1]を一見すると配列ですが、各要素のタイプと長さは異なっています。

  • u4 magic: マジックナンバーです。このファイルはpng画像ファイルではなく、JavaソースをコンパイルしたJava classファイルであることを示します。4バイトの0xCAFEBABEで固定です。
  • u2 major_version: 使用されるクラスファイルフォーマットのメジャーバージョン数です。
    • J2SE 7 = 51(0x33 十六進)
    • J2SE 6.0 = 50(0x32 十六進)
    • J2SE 5.0 = 49(0x31 十六進)
    • JDK 1.4 = 48(0x30 十六進)
  • u2 constant_pool_count: 定数プールのカウントです。
  • cp_info constant_pool[constant_pool_count-1]: 定数プールテーブル、リテラル数、文字列、そしてクラスやメソッドへの参照といった項目を含む、可変長の定数プールエントリです。 合計エントリ(定数テーブルカウント – 1)数を含む、1から始まり索引付けされます。Java SE 7 Editionに14種類のcp_infoはあります。tagの値で区別します。

    種類 tag 内容
    CONSTANT_Utf8_info 1 UTF-8 (Unicode) 文字列
    CONSTANT_Integer_info 3 Integer : Big-Endianフォーマットによる符号付き32ビット2の補数
    CONSTANT_Float_info 4 Float : 32ビット単精度IEEE 754浮動小数点数
    CONSTANT_Long_info 5 Long : Big-Endianフォーマットによる符号付き64ビット2の補数(定数テーブルの2つのスロットを占める)
    CONSTANT_Double_info 6 Double : 64ビット倍精度IEEE 754浮動小数点数(定数テーブルの2つのスロットを占める)
    CONSTANT_Class_info 7 クラス参照 : (内部フォーマットによる)完全修飾型クラス名を含むUTF-8文字列による定数テーブル内のインデックス(Big-Endian)
    CONSTANT_String_info 8 文字列参照 : UTF-8による定数プール内のインデックス(Big-Endian)
    CONSTANT_Fieldref_info 9 フィールド参照 : 定数プール内にある2つのインデックス、最初はクラス参照で次は名前および型の記述(Big-Endian)
    CONSTANT_Methodref_info 10 メソッド参照 : 定数プール内にある2つのインデックス、最初はクラス参照で次は名前および型の記述(Big-Endian)
    CONSTANT_InterfaceMethodref_info 11 インタフェース参照 : 定数プール内にある2つのインデックス、最初はクラス参照で次は名前および型の記述(Big-Endian)
    CONSTANT_NameAndType_info 12 名前および型の記述 : UTF-8による定数プール内のインデックス、最初は名前(識別子)を表し次は特別にエンコードされた型
    CONSTANT_MethodHandle_info 15 Java SE 7からinvokedynamicの対応
    CONSTANT_MethodType_info 16 Java SE 7からinvokedynamicの対応
    CONSTANT_InvokeDynamic_info 17 Java SE 7からinvokedynamicの対応

    各cp_infoの詳細は後ほど使われる際に説明します。

  • u2 access_flags: ビットマスクによるアクセスフラグです。

    フラグ キーワード
    ACC_PUBLIC 0x0001 public
    ACC_FINAL 0x0010 final
    ACC_SUPER 0x0020 super
    ACC_INTERFACE 0x0200 interface
    ACC_ABSTRACT 0x0400 abstract
    ACC_SYNTHETIC 0x1000 synthetic
    ACC_ANNOTATION 0x2000 annotation
    ACC_ENUM 0x4000 enum
  • u2 this_class: 定数プールにthisクラスの参照(CONSTANT_Class_info)

1
2
3
4
CONSTANT_Class_info {
    u1 tag;                    // 7
    u2 name_index;             // 定数プールのインデックス
}
  • u2 super_class: 親クラスの参照(CONSTANT_Class_info)
  • u2 interface_counts: 実現したインタフェース数
  • u2 interface[interface_counts]: インタフェース参照(CONSTANT_Class_info)
  • u2 fields_count:クラス変数とインスタンス変数の個数
  • field_info fields[fields_count]:フィールド参照(field_info)
1
2
3
4
5
6
7
field_info {
    u2             access_flags;                 // 変数のアクセスフラグ
    u2             name_index;                   // 変数名の定数プールのインデックス(CONSTANT_Utf8_info)
    u2             descriptor_index;             // 変数タイプの定数プールのインデックス(CONSTANT_Utf8_info)
    u2             attributes_count;             // 属性数
    attribute_info attributes[attributes_count]; // 属性参照(annotationなど)
}

アクセスフラグはここを参照できます。

  • u2 methods_count: メソッド数
  • method_info methods[methods_count]: メソッド参照(method_info)
1
2
3
4
5
6
7
method_info {
    u2             access_flags;                  // メソッドのアクセスフラグ
    u2             name_index;                    // メソッド名の定数プールのインデックス
    u2             descriptor_index;              // メソッド定義文字列の定数プールのインデックス
    u2             attributes_count;              // 属性数
    attribute_info attributes[attributes_count];  // 属性参照(annotation, excpetionなど)
}

アクセスフラグはここを参照できます。

  • u2 attributes_count: 任意の属性数
  • attribute_info attributes[attributes_count]: 任意の属性参照

    attribute_infoはクラスファイルの最後に置かれます。例外、ソース行番号、デバッグ情報、annotationなどの標準属性以外、ユーザ定義の属性もあります。

    そして属性の長さは固定ではありません。属性にネスト属性を含むことも可能です。

サンプル

話はやや複雑になりますが、簡単な例をあげます。 以下の簡単なクラスを作成します。シンプルなクラスなので、annotation, 例外処理などがありません。

簡単なクラス - Sample.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package net.codemelon.brisk.demo.jvm;

/**
 * Sample class for interpret JVM class file structure.
 *
 * @author : Haidong Wang
 */
public class Sample {

    private String name;

    public static final String AOP_CLASS_SUFFIX = "$$_brisk_aop_enhanced";

    protected int age = 30;

    public void init(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return this.name;
    }

    public static String getAopClassSuffix() {
        return Sample.AOP_CLASS_SUFFIX;
    }
}

jdk_1.7.0_40でコンパイルしたクラスは以下のようです。

Java仮想マシン仕様を参照しながら、上記のバイトコードを解読してみましょう。

  • マジック・ナンバー
    クラスファイルの先頭4バイトは、Javaのクラスファイルであることを示すマジックナンバーで、0xCAFEBABE固定です。

0000: CA FE BA BE 00 00 00 33 00 21 0A 00 06 00 1B 09 …….3.!……

  • バージョン番号
    次の4バイトは、クラスファイルが実行対象とするJavaバージョンを識別するバージョン番号です。前半2バイトがマイナー・バージョンで後半2バイトがメジャーバージョンとなります。
    以下は、マイナーバージョンが0(0x0000)、メジャーバージョンが51(0x0033)を表します。上のテーブルによって、Java SE 7のバージョン番号は51ですね。

0000: CA FE BA BE 00 00 00 33 00 21 0A 00 06 00 1B 09 …….3.!……

  • 定数プール数
    リテラル、実行時に解決するメソッド、フィールド参照、などの各種定数を持つ定数プールの個数です。定数プールは1から数えますので、Sampleクラスは32(0x21 – 1)個の定数があります。

0000: CA FE BA BE 00 00 00 33 00 21 0A 00 06 00 1B 09 …….3.!……

  • 定数プールの情報配列
    定数プールのフォーマットは種類により異なります。種類は先頭1バイトのタグで決まります。
    定数プールの詳細をみってみましょう。まず1番目の定数をみます。

0000: CA FE BA BE 00 00 00 33 00 21 0A 00 06 00 1B 09 …….3.!……

0x0aは10ですので、上のテーブルによってConstant_Methodref_infoの定数です。Constant_Methodref_infoの構造は以下のようです。

1
2
3
4
5
CONSTANT_Methodref_info {
    u1 tag;                 // 10
    u2 class_index;         // メソッド所属クラスのインデックス
    u2 name_and_type_index; // メソッド定義の定数のインデックス
}

class_indexは0x06です。定数プールの6番目のCONSTANT_Class_info定数を指します。

0000: CA FE BA BE 00 00 00 33 00 21 0A 00 06 00 1B 09 …….3.!……

name_and_type_indexは0x1bです。定数プールの27番目のCONSTANT_NameAndType_info定数を参照します。

0000: CA FE BA BE 00 00 00 33 00 21 0A 00 06 00 1B 09 …….3.!……

定数プールの27番のデータは以下のようです。

0140: 6C 65 2E 6A 61 76 61 0C 00 0D 00 0E 0C 00 0B 00 le.java………

CONSTANT_NameAndType_infoの構造は以下のようです。

1
2
3
4
5
CONSTANT_NameAndType_info {
    u1 tag;                    // 12
    u2 name_index;             // メソッド名または変数名の定数プールのインデックス
    u2 descriptor_index;       // メソッドまたは変数のメソッドの引数の型と個数,及び戻り値の型(voidを含む)
}

上記の方法を従って、すべての定数を解読できます。バイナリは読みづらいですが、JDKに便利なのツールを提供しています。
javap -version Sample.classでバイナリデータをJVMのアセンブリコードに変換します。定数プールの内容もリストアプされます。

[Sampleクラスの定数プール]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class net.codemelon.brisk.demo.jvm.Sample
  SourceFile: "Sample.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#27         //  java/lang/Object."<init>":()V
   #2 = Fieldref           #5.#28         //  net/codemelon/brisk/demo/jvm/Sample.age:I
   #3 = Fieldref           #5.#29         //  net/codemelon/brisk/demo/jvm/Sample.name:Ljava/lang/String;
   #4 = String             #30            //  $$_brisk_aop_enhanced
   #5 = Class              #31            //  net/codemelon/brisk/demo/jvm/Sample
   #6 = Class              #32            //  java/lang/Object
   #7 = Utf8               name
   #8 = Utf8               Ljava/lang/String;
   #9 = Utf8               AOP_CLASS_SUFFIX
  #10 = Utf8               ConstantValue
  #11 = Utf8               age
  #12 = Utf8               I
  #13 = Utf8               <init>
  #14 = Utf8               ()V
  #15 = Utf8               Code
  #16 = Utf8               LineNumberTable
  #17 = Utf8               LocalVariableTable
  #18 = Utf8               this
  #19 = Utf8               Lnet/codemelon/brisk/demo/jvm/Sample;
  #20 = Utf8               init
  #21 = Utf8               (Ljava/lang/String;I)V
  #22 = Utf8               getName
  #23 = Utf8               ()Ljava/lang/String;
  #24 = Utf8               getAopClassSuffix
  #25 = Utf8               SourceFile
  #26 = Utf8               Sample.java
  #27 = NameAndType        #13:#14        //  "<init>":()V
  #28 = NameAndType        #11:#12        //  age:I
  #29 = NameAndType        #7:#8          //  name:Ljava/lang/String;
  #30 = Utf8               $$_brisk_aop_enhanced
  #31 = Utf8               net/codemelon/brisk/demo/jvm/Sample
  #32 = Utf8               java/lang/Object

定数プールに32個定数があることをわかりますね。#1から数えますが、#0はクラスが定数プールを参照しないことを示します。 ですから、constant_pool_countの値は33(0x21)です。

上の1番目の定数のclass_indexは#6ですが、java.lang.Objectのメソッドをわかります。

完全修飾されたJavaのクラス名は、「java.lang.Object」のように慣例的にドットで区分けされますが、Java仮想マシン内部形式は「java/lang/Object」のように、代わりにスラッシュを使用します。

name_and_type_indexは#27のCONSTANT_NameAndType_info(上記のコードによると<init>メソッド)です。コンパイルの時に自動生成したデフォールトコンストラクターですね。
メソッドの引数と戻り値は()Vを指します。Java仮想マシン内部でデータタイプの表示を以下の表にまとめました。

BaseType Character Type Interpretation
B byte signed byte
C char Unicode character
D double double-precision floating-point value
F float single-precision floating-point value
I int integer
J long long integer
LClassname; reference an instance of class Classname
S short signed short
Z boolean true or flase
[ reference one array dimension(一次元配列)
V void return void

上の表によって、()Vは引数無し、戻り値無しの意味です。
複雑な例をあげます。二次元配列String[][]は[[Ljava/lang/Stringを表します。int[][]なら[[Iを表します。

  • アクセスフラグ
    クラス宣言またはインタフェース宣言で使用する修飾子のビットマスクを表します。

01A0: 2F 4F 62 6A 65 63 74 00 21 00 05 00 06 00 00 00 /Object.!…….

Sampleクラスのアクセスフラグは0x0021 = 0x0001|0x0020(すなわち、ACC_PUBLIC|ACC_SUPER)です。
ACC_PUBLICはpublicですが、ACC_SUPERはJDK 1.2以降強制的に追加された修飾子です。

  • this_class
    Sampleクラス情報のインデックスです。

01A0: 2F 4F 62 6A 65 63 74 00 21 00 05 00 06 00 00 00 /Object.!…….

定数プールの#5はCONSTANT_Class_info定数です。クラス名name_indexは#31のCONSTANT_Utf8_info定数を参照します。
それによって、クラスの名がnet/codemelon/brisk/demo/jvm/Sampleであることはわかります。

  • 親クラス

01A0: 2F 4F 62 6A 65 63 74 00 21 00 05 00 06 00 00 00 /Object.!…….

定数#6はjava/lang/Objectです。宣言していない場合、暗黙でjava.lang.Objectを継承しますね。

  • インタフェース
    インタフェース数とインタフェース情報配列はクラスを実現したインタフェース情報です。Sampleクラスはinterfaceがありませんので、interfaces_countは0x00です。

01A0: 2F 4F 62 6A 65 63 74 00 21 00 05 00 06 00 00 00 /Object.!…….

  • インスタンス変数とクラス変数
    Sampleクラスは2つのインスタンス変数と1つのクラス変数(合わせて3つ)があります。親クラスの変数を含まないことを注意してください。
    変数の構造は上のfield_infoです。

01A0: 2F 4F 62 6A 65 63 74 00 21 00 05 00 06 00 00 00 /Object.!…….
01B0: 03 00 02 00 07 00 08 00 00 00 19 00 09 00 08 00 …………….

以下は変数nameの内容を示します。

もっと複雑な例としてクラス変数AOP_CLASS_SUFFIXを解析しましょう。

1
    public static final String AOP_CLASS_SUFFIX = "$$_brisk_aop_enhanced";

AOP_CLASS_SUFFIXのaccess_flagsはACC_PUBLIC | ACC_STATIC | ACC_FINAL (0x0001 | 0x0008 | 0x0010) = 0x19です。

01B0: 03 00 02 00 07 00 08 00 00 00 19 00 09 00 08 00 …………….

name_indexは0x09です。上の定数一覧によって、#9 = Utf8 AOP_CLASS_SUFFIXです。

01B0: 03 00 02 00 07 00 08 00 00 00 19 00 09 00 08 00 …………….

descriptor_indexは0x08ですが、上の定数一覧によって、#8 = Utf8 Ljava/lang/String;(java.lang.Stringインスタンス)です。

01B0: 03 00 02 00 07 00 08 00 00 00 19 00 09 00 08 00 …………….

次のattributes_countは0x0001です。1つの属性はあります。

01B0: 03 00 02 00 07 00 08 00 00 00 19 00 09 00 08 00 …………….
01C0: 01 00 0A 00 00 00 02 00 04 00 04 00 0B 00 0C 00 …………….

次の2バイトは0x000aです。上の定数プールの#10によると”ConstantValue”の属性です。
ConstantValueの構造は以下のようです。

1
2
3
4
5
ConstantValue_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 constantvalue_index;
}

attribute_name_indexはConstantValueの定数プールのインデックです。attribute_lengthは固定で2です。

01C0: 01 00 0A 00 00 00 02 00 04 00 04 00 0B 00 0C 00 …………….

constantvalue_indexは0x04です。定数プールによると、#4 = String #30 // $$_brisk_aop_enhancedです。AOP_CLASS_SUFFIXの初期値ですね。

01C0: 01 00 0A 00 00 00 02 00 04 00 04 00 0B 00 0C 00 …………….

他の変数は同じな方法で解析できます。

  • メソッド
    コンパイラで自動生成したコンストラクターを含めて、methods_countは0x04です。

01D0: 00 00 04 00 01 00 0D 00 0E 00 01 00 0F 00 00 00 …………….

一番目のmethod_infoの内容を見てみましょう。method_infoの構造を上に参照できます。
access_flagsは0x01です。

01D0: 00 00 04 00 01 00 0D 00 0E 00 01 00 0F 00 00 00 …………….

メソッドのアクセスフラグ一覧によると、ACC_PUBLICは0x0001です。

01D0: 00 00 04 00 01 00 0D 00 0E 00 01 00 0F 00 00 00 …………….

name_indexは0x0dです。上の定数プールによると、#13 = Utf8 <init>です。自動生成したデフォルト・コンストラクターです。

01D0: 00 00 04 00 01 00 0D 00 0E 00 01 00 0F 00 00 00 …………….

descriptor_indexは0x0eなので、定数プールの#14 = Utf8 ()Vです。デフォルト・コンストラクターはパラメータ無し、戻り値voidです。
次のattributes_countは0x01です。

01D0: 00 00 04 00 01 00 0D 00 0E 00 01 00 0F 00 00 00 …………….

次の2バイトはattribute_name_indexです。定数プールの0x0fは#15 = Utf8 Codeです。
それによって、属性タイプはCode_attributeです。

01D0: 00 00 04 00 01 00 0D 00 0E 00 01 00 0F 00 00 00 …………….

Code_attributeの構造は以下のようです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack;
    u2 max_locals;
    u4 code_length;
    u1 code[code_length];
    u2 exception_table_length;
    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
    } exception_table[exception_table_length];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

かなり複雑な構造ですね。メソッドに仮想マシンの命令を表す構造体です。

01D0: 00 00 04 00 01 00 0D 00 0E 00 01 00 0F 00 00 00 …………….
01E0: 39 00 02 00 01 00 00 00 0B 2A B7 00 01 2A 10 1E 9……....

attribute_lengthはCode_attributeにattribute_name_indexとattribute_lengthを除いたバイト数です。
上のバイトデータによると、<init>のCode_attributeの長さは0x39バイトです。

01E0: 39 00 02 00 01 00 00 00 0B 2A B7 00 01 2A 10 1E 9……....

次のmax_stackは0x02です。max_localsは0x01です。JVMでメソッドをframeに実行されます。
frameにローカル変数用の配列と操作命令スタックはあります。変数配列はメソッドパラメータ、ローカル変数(中間結果)を保存します。
操作スタックは仮想マシンの命令と操作数(変数配列からロードされる)を順番でロードして実行します。結果を変数配列仁保存し、命令と操作数をクリアし、次の命令を処理します。
メソッドのすべてのコードを実行する時、変数配列の最大長さはmax_localsと呼ばれます。操作スタックの最大長さはmax_stackと呼ばれます。
doubleとlongのデータは64ビットなので、max_stackとmax_localsを計算するときに注意しなければなりません。
詳しいJVMのランタイム仕組みは次回に解説させて頂きます。

01E0: 39 00 02 00 01 00 00 00 0B 2A B7 00 01 2A 10 1E 9……....

code_lengthは0x0bです。メソッドコードはcode[11]に置かれます。Code_attributeの構成によって、codeタイプはu1です。
u1の範囲は0x00 ~ 0xff(0 ~ 255)です。現在約200個のJVM命令を定義しています。
exception_table_lengthとexception_tableは例外情報です。<init>は例外宣言がありませんので、exception_table_lengthは0です。

01F0: B5 00 02 B1 00 00 00 02 00 10 00 00 00 0A 00 02 …………….

次のattributes_countは0x02です。

01F0: B5 00 02 B1 00 00 00 02 00 10 00 00 00 0A 00 02 …………….

1つ目の属性のインデックは0x10です。定数プールの#16はUtf8 LineNumberTableです。

01F0: B5 00 02 B1 00 00 00 02 00 10 00 00 00 0A 00 02 …………….

LineNumberTableの構造は以下のようです。

1
2
3
4
5
6
7
8
LineNumberTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 line_number_table_length;
    {   u2 start_pc;
        u2 line_number;
    } line_number_table[line_number_table_length];
}

attribute_lengthはattribute_name_indexとattribute_length以外のバイト数です。
バイトデータによって、attribute_lengthは10(0x0a)です。

01F0: B5 00 02 B1 00 00 00 02 00 10 00 00 00 0A 00 02 …………….

line_number_table_lengthは0x02です。

01F0: B5 00 02 B1 00 00 00 02 00 10 00 00 00 0A *00 02` …………….

line_number_tableは次の8バイトとです。start_pcはJVM命令の番号です。line_numberはソースの行番号です。

0200: 00 00 00 08 00 04 00 0E 00 11 00 00 00 0C 00 01 …………….

次の属性は定数プールの#17(0x11) = Utf8 LocalVariableTableです。

0200: 00 00 00 08 00 04 00 0E 00 11 00 00 00 0C 00 01 …………….

LocalVariableTableの構造は以下のようです。

1
2
3
4
5
6
7
8
9
10
11
LocalVariableTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 local_variable_table_length;
    {   u2 start_pc;
        u2 length;
        u2 name_index;
        u2 descriptor_index;
        u2 index;
    } local_variable_table[local_variable_table_length];
}

attribute_lengthは0x0cです。local_variable_table_lengthは0x01です。local_variable_tableに1つの変数があることはわかります。

0200: 00 00 00 08 00 04 00 0E 00 11 00 00 00 0C 00 01 …………….

次の10バイトはlocal_variable_table[1]の変数です。

0210: 00 00 00 0B 00 12 00 13 00 00 00 01 00 14 00 15 …………….

定数プールとlocal_variable_tableの構造によって、上記のデータの意味は以下のようです。

1
2
3
4
5
6
7
    {
        u2 start_pc;            // 0x0000
        u2 length;              // 0x000b
        u2 name_index;          // 0x0012    #18 = Utf8               this
        u2 descriptor_index;    // 0x0013    #19 = Utf8               Lnet/codemelon/brisk/demo/jvm/Sample;
        u2 index;               // 0x0000
    }

start_pc + lengthはメソッド実行の開始JVMコマンドの位置を表します。name_indexとdescriptor_indexはSampleインスタンスthisのことが分かります。
indexはthis変数がローカル変数配列の最初(インデックス0)位置に置かれることを示します。JVMではすべてのメソッド実行frameの変数配列の0にthis変数を置かれます。

同様のように他のメソッドを解析できます。次のバイトをみってみましょう。

0210: 00 00 00 0B 00 12 00 13 00 00 00 01 00 14 00 15 …………….

0x0001はACC_PUBLICです。0x0014は定数プールの#20 = Utf8 initです。0x0015は定数プールの#21 = Utf8 (Ljava/lang/String;I)Vです。
public void init(String name, int age)メソッドであることがわかりますね。

最後はattributes_countとattribute_info attributes[attributes_count]を見ます。

02D0: 00 00 01 00 10 00 00 00 06 00 01 00 00 00 1A 00 …………….
02E0: 01 00 19 00 00 00 02 00 1A ………

attributes_countは0x0001です。属性タイプは定数プールの#25(0x0019) = Utf8 SourceFileです。 SourceFile属性の構造は以下のようです。

1
2
3
4
5
SourceFile_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 sourcefile_index;
}

attribute_lengthは固定で2です。sourcefile_indexは定数プールの#26(0x001a) Utf8 Sample.javaです。 ソースファイルの名前はSample.javaであることはわかります。

まとめ

以上では簡単なクラスを例として、Javaクラスファイルのレイアウトを解析してみました。exception_table、annotationなどの構造を触れていません。

詳しい内容はJava仮想マシン仕様の第四章を参照すれば良いと思います。

次回はJVMランタイム仕組みを紹介していきたいです。JavaコードとJVMアセンブリコードを対照しながら、JVMの内部動作を考査してみます。

Comments