作成者:Naozary
JavaVMについて1.Javaの処理系と実行系
Javaはオブジェクト指向かつ機種非依存と いう2つの特徴をもったプログラミング言語です。JDK自身は処理系と実行系が分かれているので、その片方のみを使用するといったことも可能です。たとえ ば、オブジェクト指向でない機種非依存な開発を行う、というような組み合わせが考えられます。実行系だけを用意することによって他機種で開発したJava アプリケーションを実行することが可能になります。皆さんが知っているように、クラスファイルに適合したJavaコードを生成する処理系を作成すれば、 Java以外のコードで書かれたプログラムが既存のJava処理系と併用できます。JDKのみで開発を行う場合、Javaのプログラムは以下の2つの フェーズを経て実行されます。 |
<例1>
>type HelloWorld.java public class HelloWorld { public static void main(String[] args){ System.out.println("Hello World"); } } >javac HelloWorld.java (1)ソースファイルを中間言語形式へ変換 >java HelloWorld (2)クラスファイルを専用の実行プログラムで実行 Hello World |
通常のコンパイラは、ある特定のCPUの命令コードを出力するのですが、Javaコンパイラは、Java仮想マシンと呼ばれる仮想的なCPU命令を出力します。 |
2.クラスファイルのフォーマット Javaコンパイラは、クラスファイルを出力します。一般的に、プログラムはコードとデータで成り立っていますので、当然、クラスファイルにはデータも一緒に含まれています。そのほかにデバッグ字に役立つ情報も含まれてる場合があります。Javaバイトコードの規格書によるとクラスファイルのフォーマットは、大きく分けてファイルヘッダ、定数プール、クラスの情報、インタフェースの情報、フィールドの情報、メソッドの情報、ファイル属性情報から成り立っています。JavaVMは、このクラスファイルを読み込んで実行します。(ここでは、JavaVMとは、java.exeのことであると定義しておけばOKです。) |
<HelloWorld.classダンプリスト>
00000000 CA FE BA BE 00 00 00 32-00 1D 0A 00 06 00 0F 09 .......2........ 00000010 00 10 00 11 08 00 12 0A-00 13 00 14 07 00 15 07 ................ 00000020 00 16 01 00 06 3C 69 6E-69 74 3E 01 00 03 28 29 .....<init>...() 00000030 56 01 00 04 43 6F 64 65-01 00 0F 4C 69 6E 65 4E V...Code...LineN 00000040 75 6D 62 65 72 54 61 62-6C 65 01 00 04 6D 61 69 umberTable...mai 00000050 6E 01 00 16 28 5B 4C 6A-61 76 61 2F 6C 61 6E 67 n...([Ljava/lang 00000060 2F 53 74 72 69 6E 67 3B-29 56 01 00 0A 53 6F 75 /String;)V...Sou 00000070 72 63 65 46 69 6C 65 01-00 0F 48 65 6C 6C 6F 57 rceFile...HelloW 00000080 6F 72 6C 64 2E 6A 61 76-61 0C 00 07 00 08 07 00 orld.java....... 00000090 17 0C 00 18 00 19 01 00-0B 48 65 6C 6C 6F 20 57 .........Hello W 000000A0 6F 72 6C 64 07 00 1A 0C-00 1B 00 1C 01 00 0A 48 orld...........H 000000B0 65 6C 6C 6F 57 6F 72 6C-64 01 00 10 6A 61 76 61 elloWorld...java 000000C0 2F 6C 61 6E 67 2F 4F 62-6A 65 63 74 01 00 10 6A /lang/Object...j 000000D0 61 76 61 2F 6C 61 6E 67-2F 53 79 73 74 65 6D 01 ava/lang/System. 000000E0 00 03 6F 75 74 01 00 15-4C 6A 61 76 61 2F 69 6F ..out...Ljava/io 000000F0 2F 50 72 69 6E 74 53 74-72 65 61 6D 3B 01 00 13 /PrintStream;... 00000100 6A 61 76 61 2F 69 6F 2F-50 72 69 6E 74 53 74 72 java/io/PrintStr 00000110 65 61 6D 01 00 07 70 72-69 6E 74 6C 6E 01 00 15 eam...println... 00000120 28 4C 6A 61 76 61 2F 6C-61 6E 67 2F 53 74 72 69 (Ljava/lang/Stri 00000130 6E 67 3B 29 56 00 21 00-05 00 06 00 00 00 00 00 ng;)V.!......... 00000140 02 00 01 00 07 00 08 00-01 00 09 00 00 00 1D 00 ................ 00000150 01 00 01 00 00 00 05 2A-B7 00 01 B1 00 00 00 01 .......*........ 00000160 00 0A 00 00 00 06 00 01-00 00 00 01 00 09 00 0B ................ 00000170 00 0C 00 01 00 09 00 00-00 25 00 02 00 01 00 00 .........%...... 00000180 00 09 B2 00 02 12 03 B6-00 04 B1 00 00 00 01 00 ................ 00000190 0A 00 00 00 0A 00 02 00-00 00 04 00 08 00 05 00 ................ 000001A0 01 00 0D 00 00 00 02 00-0E ......... |
<ダンプリストの解析>
00000000: Magic number '0xcafebabe' confirmed. 00000004: Java version 50.0 | 【ヘッダ】 [00000000] Classファイルは、必ずこの値になる。 [00000004] 50.0の値は、バージョン1.6を表す(45.0の値は、JDK1.02を表す) |
00000008: Below are 29 of Constastant(s) 0000000a: Const(1), Methodref class 6 name-and-type 15 0000000f: Const(2), Fieldref class 16 name-and-type 17 00000014: Const(3), String 18 00000017: Const(4), Methodref class 19 name-and-type 20 0000001c: Const(5), Class name 21 0000001f: Const(6), Class name 22 00000022: Const(7), UTF8 "<init>" 0000002b: Const(8), UTF8 "()V" 00000031: Const(9), UTF8 "Code" 00000038: Const(10), UTF8 "LineNumberTable" 0000004a: Const(11), UTF8 "main" 00000051: Const(12), UTF8 "([Ljava/lang/String;)V" 0000006a: Const(13), UTF8 "SourceFile" 00000077: Const(14), UTF8 "HelloWorld.java" 00000089: Const(15), NameAndType name 7 descriptor 8 0000008e: Const(16), Class name 23 00000091: Const(17), NameAndType name 24 descriptor 25 00000096: Const(18), UTF8 "Hello World" 000000a4: Const(19), Class name 26 000000a7: Const(20), NameAndType name 27 descriptor 28 000000ac: Const(21), UTF8 "HelloWorld" 000000b9: Const(22), UTF8 "java/lang/Object" 000000cc: Const(23), UTF8 "java/lang/System" 000000df: Const(24), UTF8 "out" 000000e5: Const(25), UTF8 "Ljava/io/PrintStream;" 000000fd: Const(26), UTF8 "java/io/PrintStream" 00000113: Const(27), UTF8 "println" 0000011d: Const(28), UTF8 "(Ljava/lang/String;)V" | 【コンスタントプール】 [00000008] コンスタントプールエントリー数。 [0000000a]-[00000134] コンスタントプールエントリ |
Belows are class information 00000135: Access Flags = 0x21 (Public, Syhnchronized) 00000137: This Class #5 00000139: Super Class #6 0000013b: 0 Interface(s) 0000013d: 0 Field(s) 0000013f: 2 Method(s) | 【クラス情報】 [00000135] クラス属性 [00000137] クラスの名前定義 [00000139] 親クラスの名前定義 [0000013b] インターフェース数 [0000013d] フィールド数 [0000013f] メソッド数(mainとコンストラクタで2個) |
Method 0: 00000141: Access Flag = 0x0001(Public) 00000143: name #7 00000145: descriptor #8 00000147: 1 method attribute(s): 00000149: name = #9 0000014b: length = 29 0000014f: max Stack 1 00000151: max Locals 1 00000153: code length = 5 00000157: 00000000 2a aload_0 00000158: 00000001 b7 invokespecial #1 0000015b: 00000004 b1 return 0000015c: 0 Exception entrie(s) 0000015e: 1 Code Attribute(s) 00000160: name = #10 00000162: length = 6 0x00 0x01 0x00 0x00 0x00 0x01 | 【メソッド情報】 [00000141] メソッドの属性 [00000143] メソッドの名前定義 <init>はコンストラクタを表す [00000145] 引数と戻り値 ()Vは、引数なし、戻り値なし [00000147] メソッド属性の個数 [00000149] “Code”で命令コードを示す [0000014b] 属性の大きさ29バイト0000014f~0000016b [0000014f] スタックの大きさ [00000151] ローカル変数の大きさ [00000153] コード本体の大きさ [00000157]-[0000015b] Java仮想コード [0000015c] 例外のエントリー数 [0000015e]-[0000016d] コードに対する付加情報。各javaバイトコードとソースコード上の位置を関連づける情報。最初の2バイトが行数で、コード開始、Javaソース行となる。 |
Method 1: 0000016c: Access Flag = 0x0009(Public, Static) 0000016e: name #11 00000170: descriptor #12 00000172: 1 method attribute(s): 00000174: name = #9 00000176: length = 37 0000017a: max Stack 2 0000017c: max Locals 1 0000017e: code length = 9 00000182: 00000000 b2 getstatic #2 00000185: 00000003 12 ldc #3 00000187: 00000005 b6 invokevirtual #4 0000018a: 00000008 b1 return 0000018b: 0 Exception entrie(s) 0000018d: 1 Code Attribute(s) 0000018f: name = #10 00000191: length = 10 0x00 0x02 0x00 0x00 0x00 0x04 0x00 0x08 0x00 0x05 | 省略 |
0000019f: 1 class file attribute(s) 000001a1: name #13 000001a3: length = 2 000001a7: sourceFile = #14 | 【クラス属性情報】 |
ダンプリストの解析にある太字部分がメソッドのアセンブラを示しているが、この部分のみの解析であれば、javapコマンドでも結果が得られる。 |
<例2>
>javap -c HelloWorld Compiled from "HelloWorld.java" public class HelloWorld extends java.lang.Object{ public HelloWorld(); Code: 0: aload_0 ① 1: invokespecial #1; //Method java/lang/Object."<init>":()V ② 4: return ③ public static void main(java.lang.String[]); Code: 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 ⑦ } |
3.アセンブラプログラムの解読(例2のアセンブラを解説する。) ①0番目のローカル変数からオブジェクト参照を取り出してスタックに積む。0番目の変数には実行中のオブジェクトへのポインタ(this)が格納されており、メソッドの呼び出しではこれを第1引数として使用する。 ②スーパークラスの<init>メソッドを呼び出すコード。JavaVMは、オブジェクト指向CPUなので、マシン語レベルでオブジェクト指向のクラスという概念が出てきます。 ③メソッド呼び出しを終了する。 ④クラスフィールドの値を、オペランドスタックに積む。 ⑤オペランドの定数をオペランドスタックに積む。 ⑥インスタンスメソッドを呼び出す。 ⑦メソッド呼び出しを終了する。 |
・Java言語のコンストラクタは、JavaVMの<init>メソッドになります。
・①~③は、インスタンス初期化メソッドで、インスタンスがヒープに割り当てられた直後に実行されるJavaVMメソッドです。
4.JavaVMメモリ構成
4.1 クラスローダについて
JavaVMは、クラスファイルを読み込んでそれを実行します。JavaVMにクラスファイルをロードする方式は、
大きくわけて2つあります。
・システムクラスローダ
VMに元から組み込まれているクラスのローディング機構を使ってクラスをロードするもので、CLASSPATHで指定さ
れた場所からクラスを読み込みます。JavaVM中に一つだけ存在する。
・ユーザクラスローダ
CLASSPATH以外の場所からクラスを読み込むときに使用する。ネットワーク経由、外部ファイル、メモリ上などから
クラスを読み込むことなども可能。JavaVM中に複数の存在が可能。
(実際にユーザクラスローダはJava.lang.ClassLoaderのインスタンスなのでヒープに存在します。)
4.2 メソッドエリアとヒープ
・メソッドエリア
メモリ中のクラスの置き場所。JavaVMはクラスファイルを読み込むと、クラスをメモリに展開します。
・ヒープ
インスタンスの置き場所。メソッドエリアに置かれたクラスのインスタンス生成時にここに置かれる。
4.3 Javaスタックとフレーム
・Javaスタック
スレッドが1つ生成されると、そのスレッドのJavaスタックが1つ生成されます。スレッドとJavaスタッ
クの関係は、常に1対1です。Javaスタックは、フレームを積み上げたものです。
・フレーム
メソッドの作業用メモリのこと。メソッド実行時に、対応するフレームが1つ作られ、終了時に破棄されます。
<例>あるJavaのスレッド(スレッドT)がrun()というメソッドを実行し、MethodC実行中のJavaスタックの状態
4.4 ローカル変数とオペランドスタック
・ローカル変数(JavaVM)
JAVA言語コンパイラがJAVA言語のメソッドを変換してJavaVMのメソッドを生成するとき、Java
言語のローカル変数とメソッド引数をJavaVMのローカル変数に割り当てます。JavaVMのローカル変数は、
名前ではなく、番号で指定されます。この番号は0から始まる整数です。ローカル変数の個数は、コンパイラの最
適化の余地がない場合に、次のように決定される。
JavaVMローカル変数の個数 = Java言語メソッド引数の個数 + Java言語のローカル変数の個数 + 1
+1されているのは、暗黙的に渡されるthisという引数がある為。(インスタンスメソッドの場合のみ)
<例>次のSampleMethod1メソッドをコンパイルすると、ローカル変数のサイズは3になります。まず暗黙
の引数thisで1つ、引数aで1つ、ローカル変数bで1つの合計3つになります。
javapでアセンブラを出力すると
5.JavaVMのマシン語
5.1 メソッドの実行例
JavaVMの使うメモリについてある程度理解できたところで、どのようにそのメモリを使用して
処理を行うかを前頁のSampleMethod1メソッドの部分で見ていきましょう。
(メソッドに対応するフレームをフレームXとする)
(0)実行前
(1)bipush 10
(2)istore_2
(3)iload_1
(4)iload_2
(5)iadd
(6)ireturn
a)スタックからPOPし、それを呼び出し元のオペランドスタックにPUSHする。
b)このカレントメソッドのフレームXが破棄され、呼び出し元のメソッドのフレームが復元され、実行制御が呼び出し元に戻ります。
[参考書籍]
Javaバーチャルマシン
Java Developer 2002/08
0 件のコメント:
コメントを投稿