HelloWorldで学ぶJavaのクラスファイル
暇つぶしにクラスファイルについて語ってみよう。
今回の勉強の元となるソースはこちら。
三分でクッキングできるほど甘くないですよ。
このエントリーを書くのに実際は丸一日もかけて・・・
その情熱を人材育成に使えって話だ。
ソース(便宜上、行番号を添えてます)
1:public class HelloWorld {
2: public static void main(String args) {
3: System.out.println("Hello World!");
4: }
5:}
上記のソースはコンパイルを終えるとこんな感じのバイナリになりました。
両者のjavapによる結果も同じものになりました。
Eclipseだとバイナリからして全然違うんですけどね。
バイナリファイル
CAFEBABE 00000032 001D0A00 06000F09 00100011 0800120A 00130014 07001507 00160100 063C696E 69743E01 00032829 56010004 436F6465 01000F4C 696E654E 756D6265 72546162 6C650100 046D6169 6E010016 285B4C6A 6176612F 6C616E67 2F537472 696E673B 29560100 0A536F75 72636546 696C6501 000F4865 6C6C6F57 6F726C64 2E6A6176 610C0007 00080700 170C0018 00190100 0C48656C 6C6F2057 6F726C64 2107001A 0C001B00 1C01000A 48656C6C 6F576F72 6C640100 106A6176 612F6C61 6E672F4F 626A6563 74010010 6A617661 2F6C616E 672F5379 7374656D 0100036F 75740100 154C6A61 76612F69 6F2F5072 696E7453 74726561 6D3B0100 136A6176 612F696F 2F507269 6E745374 7265616D 01000770 72696E74 6C6E0100 15284C6A 6176612F 6C616E67 2F537472 696E673B 29560021 00050006 00000000 00020001 00070008 00010009 0000001D 00010001 00000005 2AB70001 B1000000 01000A00 00000600 01000000 01000900 0B000C00 01000900 00002500 02000100 000009B2 00021203 B60004B1 00000001 000A0000 000A0002 00000003 00080004 0001000D 00000002 000E
クラスファイルフォーマットの話をするので確かな資料を示しましょう。
これはJVM SpecのSecond Editionからの引用になります。
クラスファイルフォーマット(一部)
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]; }
全ての情報を引用するのは厳しいので上記はその一部だと考えてください。
このさき、コンスタントプールのところは特に理解しにくいでしょう。
先頭1バイトで種類が分かり、それに応じてレイアウトが変わります。
マッピング
CAFEBABE | u4 magic | CAFEBABE 0000 | u2 ClassFile minor_version = 0 0032 | u2 ClassFile major_version = 50 001D | u2 ClassFile constant_pool_count = 29
コンスタントプールはここから。
0A | u1 CONSTANT_Methodref_info tag #1 | 0A = CONSTANT_Methodref 00 06 | u2 CONSTANT_Methodref_info class_index | 0006 = #6 000F | u2 CONSTANT_Methodref_info name_and_type_index | 000F = #15 09 | u1 CONSTANT_Fieldref_info tag #2 | 09 = CONSTANT_Fieldref 0010 | u2 CONSTANT_Fieldref_info class_index | 0010 = #16 0011 | u2 CONSTANT_Fieldref_info name_and_type_index | 0011 = #17 08 | u1 CONSTANT_String_info tag #3 | 08 = CONSTANT_String 0012 | u2 CONSTANT_String_info string_index | 0012 = #18 0A | u1 CONSTANT_Methodref_info tag #4 | 0A = CONSTANT_Methodref 0013 | u2 CONSTANT_Methodref_info class_index | 0013 = #19 0014 | u2 CONSTANT_Methodref_info name_and_type_index | 0014 = #20 07 | u1 CONSTANT_Class_info tag #5 | 07 = CONSTANT_Class 0015 | u2 CONSTANT_Class_info name_index | 15 = #21 07 | u1 CONSTANT_Class_info tag #6 | 07 = CONSTANT_Class 0016 | u2 CONSTANT_Class_info name_index | 16 = #22 01 | u1 CONSTANT_Utf8_info tag #7 | 01 = CONSTANT_Utf8 00 06 | u2 CONSTANT_Utf8_info length | 0006 = 6 3C696E 69743E | u6 CONSTANT_Utf8_info bytes | 3C696E69743E | < i n i t > 01 | u1 CONSTANT_Utf8_info tag #8 | 01 = CONSTANT_Utf8 00 03 | u2 CONSTANT_Utf8_info length | 0003 = 3 2829 | u3 CONSTANT_Utf8_info bytes 56 | 282956 | ( ) V 01 | u1 CONSTANT_Utf8_info tag #9 | 01 = CONSTANT_Utf8 0004 | u2 CONSTANT_Utf8_info length | 0004 = 4 436F6465 | u4 CONSTANT_Utf8_info bytes | 436F6465 | C o d e 01 | u1 CONSTANT_Utf8_info tag #10 | 01 = CONSTANT_Utf8 000F | u2 CONSTANT_Utf8_info length | 000F = 15 4C 696E654E | u15 CONSTANT_Utf8_info bytes 756D6265 72546162 6C65 | 4C696E654E756D6265725461626C65 | L i n e N u m b e r T a b l e 01 | u1 CONSTANT_Utf8_info tag #11 | 01 = CONSTANT_Utf8 00 04 | u2 CONSTANT_Utf8_info length | 0004 = 4 6D6169 | u4 CONSTANT_Utf8_info bytes 6E | 6D61696E | m a i n 01 | u1 CONSTANT_Utf8_info tag #12 | 01 = CONSTANT_Utf8 0016 | u2 CONSTANT_Utf8_info length | 0016 = 22 285B4C6A 6176612F 6C616E67 | u22 CONSTANT_Utf8_info bytes 2F537472 696E673B 2956 | 285B4C6A6176612F6C616E672F537472696E673B2956 | ( [ L j a v a / l a n g / S t r i n g ; ) V 01 | u1 CONSTANT_Utf8_info tag #13 | 01 = CONSTANT_Utf8 00 0A | u2 CONSTANT_Utf8_info length | 000A = 10 536F75 | u10 CONSTANT_Utf8_info bytes 72636546 696C65 | 536F7572636546696C65 | S o u r c e F i l e 01 | u1 CONSTANT_Utf8_info tag #14 | 01 = CONSTANT_Utf8 000F | u2 CONSTANT_Utf8_info length | 000F = 15 4865 6C6C6F57 | u15 CONSTANT_Utf8_info bytes 6F726C64 2E6A6176 61 | 48656C6C6F576F726C642E6A617661 | H e l l o W o r l d . j a v a 0C | u1 CONSTANT_NameAndType_info tag #15 | 0C = CONSTANT_NameAndType 0007 | u2 CONSTANT_NameAndType_info name_index | 0007 = #7 0008 | u2 CONSTANT_NameAndType_info descriptor_index | 0008 = #8 07 | u1 CONSTANT_Class_info tag #16 | 07 = CONSTANT_Class 00 | u2 CONSTANT_Class_info name_index 17 | 0017 = #23 0C | u1 CONSTANT_NameAndType_info tag #17 | 0C = CONSTANT_NameAndType 0018 | u2 CONSTANT_NameAndType_info name_index | 0018 = #24 0019 | u2 CONSTANT_NameAndType_info descriptor_index | 0019 = #25 01 | u1 CONSTANT_Utf8_info tag #18 | 01 = CONSTANT_Utf8 00 0C | u2 CONSTANT_Utf8_info length | 000C = 12 48656C 6C6F2057 | u12 CONSTANT_Utf8_info bytes 6F726C64 21 | 48656C6C6F20576F726C6421 | H e l l o W o r l d ! 07 | u1 CONSTANT_Class_info tag #19 | 07 = CONSTANT_Class 001A | u2 CONSTANT_Class_info name_index | 001A = #26 0C | u1 CONSTANT_NameAndType_info tag #20 | 0C = CONSTANT_NameAndType 001B | u2 CONSTANT_NameAndType_info name_index | 001B = #27 00 1C | u2 CONSTANT_NameAndType_info descriptor_index | 001C = #28 01 | u1 CONSTANT_Utf8_info tag #21 | 01 = CONSTANT_Utf8 000A | u2 CONSTANT_Utf8_info length | 000A = 10 48656C6C 6F576F72 6C64 | u10 CONSTANT_Utf8_info bytes | 48656C6C6F576F726C64 | H e l l o W o r l d 01 | u1 CONSTANT_Utf8_info tag #22 | 01 = CONSTANT_Utf8 00 10 | u2 CONSTANT_Utf8_info length | 0010 = 16 6A6176 | u16 CONSTANT_Utf8_info bytes 612F6C61 6E672F4F 626A6563 74 | 6A6176612F6C616E672F4F626A656374 | j a v a / l a n g / O b j e c t 01 | u1 CONSTANT_Utf8_info tag #23 | 01 = CONSTANT_Utf8 0010 | u2 CONSTANT_Utf8_info length | 0010 = 16 6A617661 2F6C616E 672F5379 7374656D | u16 CONSTANT_Utf8_info bytes | 6A6176612F6C616E672F53797374656D | j a v a / l a n g / S y s t e m 01 | u1 CONSTANT_Utf8_info tag #24 | 01 = CONSTANT_Utf8 0003 | u2 CONSTANT_Utf8_info length | 0003 = 3 6F 7574 | u3 CONSTANT_Utf8_info bytes | 6F7574 | o u t 01 | u1 CONSTANT_Utf8_info tag #25 | 01 = CONSTANT_Utf8 00 15 | u2 CONSTANT_Utf8_info length | 0015 = 21 4C6A61 76612F69 | u21 CONSTANT_Utf8_info bytes 6F2F5072 696E7453 74726561 6D3B | 4C6A6176612F696F2F5072696E7453747265616D3B | L j a v a / i o / P r i n t S t r e a m 01 | u1 CONSTANT_Utf8_info tag #26 | 01 = CONSTANT_Utf8 00 | u2 CONSTANT_Utf8_info length 13 | 0013 = 19 6A6176 612F696F 2F507269 6E745374 | u19 CONSTANT_Utf8_info bytes 7265616D | 6A6176612F696F2F5072696E7453747265616D | j a v a / i o / P r i n t S t r e a m 01 | u1 CONSTANT_Utf8_info tag #27 | 01 = CONSTANT_Utf8 0007 | u2 CONSTANT_Utf8_info length | 0007 = 7 70 72696E74 6C6E | u7 CONSTANT_Utf8_info bytes | 7072696E746C6E | p r i n t l n 01 | u1 CONSTANT_Utf8_info tag #28 | 01 = CONSTANT_Utf8 00 | u2 CONSTANT_Utf8_info length 15 | 0015 = 21 284C6A 6176612F 6C616E67 2F537472 | u19 CONSTANT_Utf8_info bytes 696E673B 2956 | 284C6A6176612F6C616E672F537472696E673B2956 ( L j a v a / l a n g / S t r i n g ; ) V
ここまでがコンスタントプール。全部で28個プールされてます。
コンスタントプールにおさめられる情報の種類はこちら。
あとで出てきますがjavapにでてくるAscizというのはCONSTANT_Utf8のことです。
コンスタントプールを直訳するなら定数プールになってしまいますが、よく使われている定数とは性質が異なるので、変な誤解を与えるのが嫌でコンスタントプールという言葉を使うことを避けてます。JVM方面にも腕のたつJavaエンジニアにしか使わないほうがいい、あとはバイトコード操作するライブラリを使った経験のある人とか。
0021 | u2 ClassFile access_flags | 0021 = 0020 ACC_SUPER | 0001 ACC_PUBLIC 0005 | u2 ClassFile this_class | #5 => #21 HelloWorld 0006 | u2 ClassFile super_class #6 0000 | u2 ClassFile interfaces_count 0 0000 | u2 ClassFile fields_count 0 0002 | u2 ClassFile method_count 2
インターフェースやフィールドがないので若干さみしいクラスファイルになってます。メソッドカウントが2なので、ここからはメソッドの情報です。
メソッド情報の1つ目(暗黙で生成されたコンストラクタ)
0001 | u2 method_info access_flags | 0001 = 0001 ACC_PUBLIC 0007 | u2 method_info name_index | #7 <init> 0008 | u2 method_info descriptor_index | #8 ()V 0001 | u2 method_info attributes_count 0009 | u2 Code_attribute attribute_name_index | #9 Code 0000001D | u4 Code_attribute attribute_length = 29 0001 | u2 Code_attribute max_stack = 1 0001 | u2 Code_attribute max_locals = 1 00000005 | u4 Code_attribute code_length = 5 2AB70001 B1 | u5 Code_attribute code 0000 | u2 Code_attribute exception_table_length = 0 00 | u2 Code_attribute attributes_count = 1 01 | 000A | u2 LineNumberTable_attribute attribute_name_index | #10 = LineNumberTable 00 000006 | u4 LineNumberTable_attribute attribute_length = 6 00 01 | u2 LineNumberTable_attribute line_number_table_length 0000 | u2 LineNumberTable_attribute start_pc 00 01 | u2 LineNumberTable_attribute line_number
method_infoのattribute_lengthが1で、Code_attributeがついてます。そしてCode_attributeのattribute_countも1がついており、LineNumberTable_attributeが添えられてます。コンパイラーの設定でこのLineNumberTable_attributeをつけなくすることができます。デバッグが大変なのでまずやらないですね。
お気づきでしょうが、ソース上に書いてないのに行数がでます。このように暗黙で作られたものは1行目とみなされます。他の例でいくと、総称メソッドなんかも暗黙でメソッドが作られるのでこういうのになります。
method_infoのattribute一覧
- Code_attribute
- Exceptions_attribute これはthrows句の部分です
- Synthetic_attribute 合成メソッド。例えば総称メソッドで合成メソッドができます。
- Deprecated_attribute これはmethod_info以外でも使われます。ClassFileとfield_infoにも。
Synthetic_attributeもClassFile、field_infoで使われます。Code_attributeとExceptions_attributeはmethod_info固有のattributeのはずです。
メソッド情報の2つ目(mainメソッド)
0009 | u2 method_info access_flags | 0009 = 00008 ACC_STATIC | 00001 ACC_PUBLIC 00 | u2 method_info name_index 0B | #11 = main 000C | u2 method_info descriptor_index | #12 = ([Ljava/lang/String;)V 00 01 | u2 method_info attributes_count 0009 | u2 Code_attribute attribute_name_index | #9 Code 00 000025 | u4 Code_attribute attribute_length 00 02 | u2 Code_attribute max_stack 0001 | u2 Code_attribute max_locals 00 | u4 Code_attribute code_length 000009 | 00000009 = 9 B2 00021203 B60004B1 | u9 Code_attribute code 0000 | u2 Code_attribute exception_table_length 0001 | u2 Code_attribute attributes_count 000A | u2 LineNumberTable_attribute attribute_name_index | #10 = LineNumberTable 0000 000A | u4 LineNumberTable_attribute attribute_length 0002 | u2 LineNumberTable_attribute line_number_table_length 0000 | u2 LineNumberTable_attribute start_pc 0003 | u2 LineNumberTable_attribute line_number 0008 | u2 LineNumberTable_attribute start_pc 0004 | u2 LineNumberTable_attribute line_number
クラスアトリビュートの部分。オプション的なもの。
0001 | u2 ClassFile attribute_count 000D | u2 SourceFile_attribute attribute_name_index | #13 = SourceFile 00000002 | u4 SourceFile_attribute attribute_length 000E | u2 SourceFile_attribute sourcefile_index | #14 = HelloWorld.java
今回のクラスファイルだと、Local Variable TableとException Tableが出てこない。
もっと厳密な話を言うと以下の構造は使われてない。
- LocalVariableTable_attribute
- Code_attributeの中のexception_table
- Exceptions_attribute ※exception_tableとは別のものです
- field_info
- Synthetic_attribute
- InnerClasses_attribute
- Deprecated_attribute
- ConstantValue_attribute
- CONSTANT_Long_info
- CONSTANT_Double_info
- CONSTANT_Float_info
- CONSTANT_Integer_info
- CONSTANT_Fieldref_info
- CONSTANT_InterfaceMethodref_info
ちなみにEclipseでのコンパイル結果では、Local Variable Tableが作られていた。
少々残念だが、これらはまたの機会にしよう。
ここから少しコード(OPCODE)の話をしよう。
今回はメソッドが2つなんだが、当然ながらmainメソッドの方を解説する。
B2 00021203 B60004B1 の部分。
そろそろjavapの内容を出さないと辛い。直接クラスファイルを読むなんて普通しないのでjavapの内容を見ながら話しましょう。現場で正規のjavapを使うのは手間なので、通常はEclipseのClass File Editorで見ます。今回のは正規のjavapです。自分がよく使うのは以下のオプション。
javap -classpath クラスパス -verbose -private クラス
Compiled from "HelloWorld.java" public class HelloWorld extends java.lang.Object SourceFile: "HelloWorld.java" minor version: 0 major version: 50 Constant pool: const #1 = Method #6.#15; // java/lang/Object."<init>":()V const #2 = Field #16.#17; // java/lang/System.out:Ljava/io/PrintStream; const #3 = String #18; // Hello World! const #4 = Method #19.#20; // java/io/PrintStream.println:(Ljava/lang/String;)V const #5 = class #21; // HelloWorld const #6 = class #22; // java/lang/Object const #7 = Asciz <init>; const #8 = Asciz ()V; const #9 = Asciz Code; const #10 = Asciz LineNumberTable; const #11 = Asciz main; const #12 = Asciz ([Ljava/lang/String;)V; const #13 = Asciz SourceFile; const #14 = Asciz HelloWorld.java; const #15 = NameAndType #7:#8;// "<init>":()V const #16 = class #23; // java/lang/System const #17 = NameAndType #24:#25;// out:Ljava/io/PrintStream; const #18 = Asciz Hello World!; const #19 = class #26; // java/io/PrintStream const #20 = NameAndType #27:#28;// println:(Ljava/lang/String;)V const #21 = Asciz HelloWorld; const #22 = Asciz java/lang/Object; const #23 = Asciz java/lang/System; const #24 = Asciz out; const #25 = Asciz Ljava/io/PrintStream;; const #26 = Asciz java/io/PrintStream; const #27 = Asciz println; const #28 = Asciz (Ljava/lang/String;)V; { public HelloWorld(); Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return LineNumberTable: line 1: 0 public static void main(java.lang.String); Code: Stack=2, Locals=1, Args_size=1 0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3; //String Hello World! 5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 3: 0 line 4: 8 }
B2 00021203 B60004B1 の部分は、getstatic、ldc、invokevirtual、returnの部分。
これらのOPCODEの説明はこちら
B20002の説明
B2=getstaticのOPCODE
00=index byte1
02=index byte2
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
index byte2が#2で、コンスタントプールの#2で表しているFieldを取得している。
getstaticの結果(PrintStream)はオペランドスタックに積まれる。
1203の説明
12=ldcのOPCODE
03=index
3: ldc #3; //String Hello World!
コンスタントプールの#3をオペランドスタックに積んでいる。
B60004の説明
B6=invokevirtualのOPCODE
00=index byte1
04=index byte2
5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
コンスタントプールの#4にあるメソッドを呼び出している。
オペランドスタックには先ほどの文字列とインスタンス(PrintStream)が積んであるので、それを使ってこのメソッドを呼び出している。
戻り値がVなので、これでオペランドスタックは空になる。
B1の説明
B1 = returnのOPCODE
この時、オペランドスタックは既に空だが、returnにはオペランドスタックを捨てる作用もある。
アセンブラを知る人はこれらの命令の形式がアセンブラに近いものであることに気付くだろう。JITでNativeコードにするのに、この形式は非常に良いのだと思う。もちろん、JITが出るまえからこういう形式なんだろうけど。
LineNumberTableとPC number
PC numberは各Instructionに振られる番号で、LineNumberTableはPC numberとソースでの行番号をマッピングしている。実行時にソース行は意味を持たないためコンパイラオプションで消せる。完全にソース行と一致するわけではない。ほかにPC Numberで触れておきたいのは、今回でてこなかったException Tableでは、どのPC numberの区間で、どんな例外が起きた場合に、どのPC Numberへジャンプするのかが書かれたものということだ。
今回でてきたOPCODEのみでは実際のものを読むには足らない。そこは地道に調べて覚えていくしかない。OPCODEやInstructionやmnemonic(ニーモニック)という単語を使うといいだろう。