この記事は何?
最近趣味でJVMを作り始めたので,その過程で得た知見とかを書いていく記事です.
今回は,クラスファイルに焦点を当てていきます.
参考文献
参考文献は一番最後に書くものだと思ってたけど,最初に書いても良いんじゃないだろうか.
- Java Virtual Machine Specification - まさしくJVMの規格書.一番信頼できるし,わからないことがあればとりあえずここを見る.
- Javaのクラスファイルをjavapとバイナリエディタで読む - 軽くクラスファイルの内部に触れてみようとして,最初に見つけた記事.とてもわかりやすかった.
クラスファイルの構造
規格より抜粋.
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
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];
}
uN
はNバイトの符号なし整数を表現しているようです.
それでは最初のフィールドから見ていきましょう.
magic
クラスファイルの先頭に位置するmagic
には定数CAFEBABE
(16進数)が入ります.CAFEBABEという値になった理由は色々あるようです.
わかりやすい値なので,これで誰でもクラスファイルと気づくことができますね.
minor_version, major_version
正直説明することがないですね.(マイナー|メジャー)バージョン情報です.
constant_pool_count
後述するconstant_pool
のエントリの数**-1**を表しています.
constant_pool
前述したconstant_pool_count
の値によって長さの変わる配列です.型もcp_info
と見慣れないものになっています.
とりあえず,cp_info
の中身を見てみましょう.
cp_info {
u1 tag;
u1 info[];
}
tag
はそのcp_info
が何を表しているのか(=Constant Type)を示すものです.info
はtag
によって大きさが変わります.
Constant Typeとtag
の値の対応は以下のようになっています.
Constant Type | tagの値 |
---|---|
CONSTANT_Class | 7 |
CONSTANT_Fieldref | 9 |
CONSTANT_Methodref | 10 |
CONSTANT_InterfaceMethodref | 11 |
CONSTANT_String | 8 |
CONSTANT_Integer | 3 |
CONSTANT_Float | 4 |
CONSTANT_Long | 5 |
CONSTANT_Double | 6 |
CONSTANT_NameAndType | 12 |
CONSTANT_Utf8 | 1 |
CONSTANT_MethodHandle | 15 |
CONSTANT_MethodType | 16 |
CONSTANT_InvokeDynamic | 18 |
tag
の値,Constant Typeは色々あって一つづつ説明していると長くなるので,とりあえずはカット.詳しく知りたい方は,規格のこのあたりからを読めば幸せになれると思います.
ハマったところとか
-
constant_pool
の0番目の要素は存在しません.もう少し詳しく言うと,0番目はデータが存在しないことを表現するのに使います.だから,constant_pool[constant_pool_count-1]
. - CONSTANT_Utf8で表されるUTF-8文字列はヌル終端じゃなかった.(C言語脳なので,文字列はNULLで終わるものだと思っていた)
- Constant Typeが
CONSTANT_Long
やConstant_Double
の時は,特殊なケースなので注意すること.
All 8-byte constants take up two entries in the constant_pool table of the class file. If a CONSTANT_Long_info or CONSTANT_Double_info structure is the item in the constant_pool table at index n, then the next usable item in the pool is located at index n+2. The constant_pool index n+1 must be valid but is considered unusable.
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4.5
access_flags
アクセス権限とかその他色々を表すフラグです.規格によると,今は8種類あるようです.
フラグの名前 | 値 | 説明 |
---|---|---|
ACC_PUBLIC | 0x0001 | publicであることを表す.パッケージの外部からアクセスされるかもしれないってことですね |
ACC_FINAL | 0x0010 | final. いかなるサブクラスも許されない,と. |
ACC_SUPER | 0x0020 |
invokespecial という命令によって呼び出された時に,スーパークラスのメソッドを扱う. |
ACC_INTERFACE | 0x0200 | インターフェース.クラスじゃないよ. |
ACC_ABSTRACT | 0x0400 | abstract.インスタンス化,ダメゼッタイ. |
ACC_SYNTHETIC | 0x1000 | synthetic.コンパイラが勝手に作る. |
ACC_ANNOTATION | 0x2000 | アノテーション型 |
ACC_ENUM | 0x4000 | enumですね |
this_class, super_class
前述のconstant_pool
のうち,
-
this_class
の表す値のインデックスに位置するのが,そのクラスの名前 -
super_class
の表す値のインデックスに位置するのが,スーパークラスの名前
となっています.
名前というのは(というかクラスファイルに含まれる文字列は)UTF-8で表されているようで,先ほど登場したConstant TypeのCONSTANT_Utf8に対応します.
interfaces_count
このクラス,またはインターフェース型の直接のスーパーインターフェースの数を表します.要するに,後述のinterfaces
の要素数です.
interfaces
interfaces
の要素の表す値は,constant_pool
のインデックスとなっています.
対応するconstant_pool
の値のConstant TypeはCONSTANT_Class_info(CONSTANT_Classが表す構造体)である必要があります.
特に面白くないですね.
fields_count
このクラス,またはインターフェース型で定義されているフィールドの数.
後述のfields
の要素数でもある.
fields
field_info
という謎の型でできた配列.定義はこうなっています.
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
attribute_info
という,またもや知らない型が出てきましたが,とりあえずは置いておきましょう.
access_flags
先程もaccess_flags
というフィールドを見た気がしますが,それとはまた別のものです.
名前 | Value | Interpretation |
---|---|---|
ACC_PUBLIC | 0x0001 | public. |
ACC_PRIVATE | 0x0002 | private.そのクラス内でのみ利用可. |
ACC_PROTECTED | 0x0004 | protected.サブクラスからアクセスされるかもね. |
ACC_STATIC | 0x0008 | static. |
ACC_FINAL | 0x0010 | final.オブジェクトが生成された後には直接代入されない(JLS §17.5になんか書いてあるらしい). |
ACC_VOLATILE | 0x0040 | volatile.キャッシュできない. |
ACC_TRANSIENT | 0x0080 | transient.persistent object managerから書き込まれたり読み込まれたりしない. |
ACC_SYNTHETIC | 0x1000 | synthetic. |
ACC_ENUM | 0x4000 | enumの要素. |
name_index, descriptor_index
両方とも,constant_pool
への参照値(インデックスですね)を表します.
name_index
の指す先には,fibo
などフィールドの名前が含まれます.
descriptor_index
の指す先には,(I)I
などの型情報が含まれます.基本的には(X)Y
という形式で,Xが引数,Yが返却値の型を表しているようです.
attribute...
また後で解説します.結構重要な位置を占めるものなので.
methods_count, methods
このクラス,またはインターフェース型で定義されているメソッドのことを表している...というのは,もはや説明の必要はないでしょう.
前述のfields_count
とfields
に似ている,といいますか,同じです.
method_info
もfield_info
と同じ.
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
attribute_count
後述するattributes
の要素数.
attributes
クラスファイルの中でも一番最後のフィールドです.そして,fields
やmethods
の中ですでに何度も現れている型であるattribute_infoで構成されています.
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
attribute_name_index
はconstant_pool
への参照値.アトリビュートの名前を表します.
attribute_length
は,info
の長さのようです.info
はアトリビュートの名前によって内容が変わります.
現在はこんなに種類のあるアトリビュート↓ この一つひとつに対応する構造体があり,それがinfo
の中に収まっているわけです.
アトリビュートの名前 |
---|
ConstantValue |
Code |
StackMapTable |
Exceptions |
InnerClasses |
EnclosingMethod |
Synthetic |
Signature |
SourceFile |
SourceDebugExtension |
LineNumberTable |
LocalVariableTable |
LocalVariableTypeTable |
Deprecated |
RuntimeVisibleAnnotations |
RuntimeInvisibleAnnotations |
RuntimeVisibleParameterAnnotations |
RuntimeInvisibleParameterAnnotations |
AnnotationDefault |
BootstrapMethods |
実践
ここまでクラスファイルの中身を見てきましたが,これだけではわかった気にはなれませんね.
実際のクラスファイルを見てみます.
以下のようなJavaコードを考えます.
class Add {
public static int add(int x, int y) {
return x + y;
}
}
コンパイルして,xxdで見てみる.
$ javac Add.java
$ xxd Add.class
00000000: cafe babe 0000 0034 000f 0a00 0300 0c07 .......4........
00000010: 000d 0700 0e01 0006 3c69 6e69 743e 0100 ........<init>..
00000020: 0328 2956 0100 0443 6f64 6501 000f 4c69 .()V...Code...Li
00000030: 6e65 4e75 6d62 6572 5461 626c 6501 0003 neNumberTable...
00000040: 6164 6401 0005 2849 4929 4901 000a 536f add...(II)I...So
00000050: 7572 6365 4669 6c65 0100 0841 6464 2e6a urceFile...Add.j
00000060: 6176 610c 0004 0005 0100 0341 6464 0100 ava........Add..
00000070: 106a 6176 612f 6c61 6e67 2f4f 626a 6563 .java/lang/Objec
00000080: 7400 2000 0200 0300 0000 0000 0200 0000 t. .............
00000090: 0400 0500 0100 0600 0000 1d00 0100 0100 ................
000000a0: 0000 052a b700 01b1 0000 0001 0007 0000 ...*............
000000b0: 0006 0001 0000 0001 0009 0008 0009 0001 ................
000000c0: 0006 0000 001c 0002 0002 0000 0004 1a1b ................
000000d0: 60ac 0000 0001 0007 0000 0006 0001 0000 `...............
000000e0: 0003 0001 000a 0000 0002 000b ............
さあ,ここまでで学んだ知識を使って読んでいきましょう...と言いたかったのですが...
ここまで作って,力尽きました.すいません.(いつか全部埋めて置き換える)
どうしても全部読みたい!という方は規格とにらめっこするか,私のコードのここらへんを参照すれば幸せになれるはず.
最後に
普段,記事を書いたりするなどのOutputがなかったので書いてみました.
一応シリーズ物なので,次はJVMがクラスファイルを読み込んでどう実行するのかについて書きたいなぁなんて思っています.
批評などコメントは大歓迎です.