2009年2月14日土曜日

JavaVMについて

作成者: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 件のコメント:

コメントを投稿