todokr
- “Hello world” の出力をを通じて、Javaについての基本を理解します
- 前提知識は不要です
- Step1. ███████████████を用意する
- Step2. ████████████████████████を用意する
- Step3. 実行する
- Step1. JavaでHello worldするコードを用意する
- Step2. ████████████████████████を用意する
- Step3. 実行する
- Step1. JavaでHello worldするコードを用意する
- Step2. JVMの仕様どおりにクラスファイルを実行するコードを用意する
- Step3. 実行する
Hello worldするコードを用意しておいてください インターネットからコピペでいいです
public class Hello {
public static void main(String[] args) {
System.out.println("Hello world");
}
}普通にコンパイルもしておいてください
javac Hello.javaまず JVMの仕様 をカジュアルな気持ちで読んでみよう
個人的なおもしろポイントをいくつか紹介します
「振り返ると、8バイトの定数に2エントリを割り当てたのは間違った選択でした」
In retrospect, making 8-byte constants take two constant pool entries was a poor choice.
はじまりは HotJava というウェブブラウザ
The HotJava browser first showcased the interesting properties of the Java programming language and platform by making it possible to embed programs inside HTML pages.
めちゃめちゃ怪しいサイト からダウンロードできるっぽい
The number of dimensions in an array is limited to 255 by the size of the dimensions opcode of the multianewarray instruction and by the constraints imposed on the multianewarray, anewarray, and newarray instructions (§4.9.1, §4.9.2).
The Specification is subject to U.S. export control laws and may be subject to export or import regulations in other countries.
キューバ, イラン, 北朝鮮, シリア, ウクライナのドンバス, ロシア, ベラルーシ, ベネズエラとかには輸出NGなので気をつけよう
「classファイルが読めて、その中のオペレーションが実行できるだけでOK」
To implement the Java Virtual Machine correctly, you need only be able to read the class file format and correctly perform the operations specified therein.
なんかできる気がしてきた
Java class ファイルとは
- Javaのソースコードをコンパイルした結果のバイナリファイル
- フォーマットは ここ に書いてある
- Hello.class を自分のバイナリエディタで開いてみよう
読める気がしないので、 javap -v しよう
The ClassFile Structure と突き合わせながら読んでみよう
- かの有名な 0xCAFEBABE
- NeXTに由来するらしい
- minor, major の順で並んでいる
- たとえば Java17 だと major_version が 61
- プレビュー版はminor_version が 65535
- cp_info内に(constant_pool_count - 1)個のエントリが入っている
- エントリは定数やシンボルなど、実行に必要な情報
- クラス名: java//lang/Object
- メソッドのシグネチャ: println:(Ljava/lang/String;)V
- 文字列リテラル: Hello world
- などなど
- access_flags: public, final, abstract とかを表す
- ビットマスクなのでパースするとき楽しい
- 0x0001: public
- 0x0010: final
- 0x0020: super
- 0x0400: abstract
- 0x1000: synthetic(コンパイラによって生成される、ソースコードには現れないやつ)
- 0x2000: annotation
- 0x4000: enum
- 0x8000: module
- ビットマスクなのでパースするとき楽しい
- this_class: このクラス(を示すcp_infoのインデックス)
- super_class: 親クラス(を示すcp_infoのインデックス)
- interfaces_count: このクラスが実装しているインターフェースの数
- interfaces: インターフェース(を示すcp_infoのインデックス)
- fields_count: このクラスが持つフィールドの数
- fields: フィールドの情報
- methods_count: このクラスが持つメソッドの数
- methods: メソッドの情報
- method_info が持つ attribute_info のうち、属性の種別が Code であるやつの中に、JVMが実行する命令が入っている
- attribute_name_indexが指すエントリをconstant_poolから取得する
- 取得したエントリを CONSTANT_Utf8_info として解釈し、文字列を得る
- その文字列が Code なら、Code_attribute としてパースする
- その中にある code が実行する命令!
各種要素をPrintする際のformatをいい感じにして、パースした結果を出力してみよう
mainメソッドが実行する命令はこれでした
main code: [ 0xb2 0x0 0x7 0x12 0xd 0xb6 0x0 0xf 0xb1 ]
The Java Virtual Machine Instruction Set を見ながら、それぞれの命令を解釈してみよう
staticフィールドをconstant poolから取得する
- そのあとの2byteは 0x0 0x07 だから indexは#7だな
- #7はCONSTANT_Field_infoで、classIndex #8, nameAndTypeIndex #9 だな
- #8はCONSTANT_Class_infoで、nameIndex #10だな
- #10は CONSTANT_Utf8_infoで “java/lang/System” だな
- #9はCONSTANT_NameAndType_infoで、nameIndex #11, descriptorIndex #12 だな
- #11は CONSTANT_Utf8_infoで “out” だな
- #8はCONSTANT_Class_infoで、nameIndex #10だな
- つまり System.out だな
- スタックに System.out を積むぜ
constant poolの値を取り出してスタックに積む
- そのあとの1byteは 0xdなのでindexは#13だな
- #13は CONSTANT_String_infoで #14だな
- #14は CONSTANT_Utf8_infoで “Hello world” だな
- #13は CONSTANT_String_infoで #14だな
- つまり “Hello world” だな
- スタックに “Hello world” を積むぜ
仮想ディスパッチ(呼ぶ対象を実行時に決定)するのでvirtual
- そのあとの2byteは 0x0 0xf だから indexは#15だな
- #15はCONSTANT_Method_infoで classIndex #16, nameAndTypeIndex #17 だな
- #16はCONSTANT_Class_infoで nameIndex #18だな
- #18はCONSTANT_Utf8_infoで “java/io/PrintStream” だな
- #17はCONSTANT_NameAndType_infoで nameIndex #19, descriptorIndex #20 だな
- #19はCONSTANT_Utf8_infoで “println” だな
- #20はCONSTANT_Utf8_infoで “(Ljava/lang/String;)V” だな
- ってことは引数がStringが1つで戻り値がvoidだな
- #16はCONSTANT_Class_infoで nameIndex #18だな
- #15はCONSTANT_Method_infoで classIndex #16, nameAndTypeIndex #17 だな
- 引数が1つなので1つpopだな
- “Hello world” が出てきたな
- つまり java.io.PrintStream.println に “Hello world” をエイッと渡してバーンと実行だな
- voidを返す
- スタックに値が残っていたら破棄する
- これで完成?
- 残念、このままでは動かせない
- オレオレJVMはそんなもの知らない
go run main.go Hello.class- Javaの基礎について学んだ
- classファイルの構造
- 実行のしくみ
- JVMの仕様を読んで、classファイルをパース & 実行した
- Hello worldだけならそんなに難しくない
- attribute_infoのパースとinvokevirtualの実装がちょっと面倒
- Hello worldだけならそんなに難しくない
- みんなも好きな言語でJVMを作ろう!
Java6のライセンスには明確に「この用途に使ってはいけない」が書かれている