本章では、JavaのスレッドのAPIを学習する前に、Javaエンジニアにとって非常に重要なJVMのアーキテクチャについて説明します。
本章の内容は、Javaにおける学習において経験年数に関わらず、抑えておきたい内容です。
JVMのアーキテクチャを理解している人のスレッド操作は、まずバグの発生率が低く、
バグや何かしらのJVMにおける問題が発生したときの問題解決能力が非常に高いです。
Java8ベースのJVMのアーキテクチャについて説明します。何度も本頁及び他の参考文献を読み理解に心がけて下さい。
本頁に関しては、Javaを初めての方には、少し理解が難しい内容ばかりです。
ただ、Javaを長く利用される方は、数年掛けても良いので本頁及び参考サイトを参考にして、
JVMの概要については、理解して下さい。まずは、概要図を流し見して下さい。
JVMのアーキテクチャについて理解は、Javaエンジニアとして1流になるためは必須の知識です。
©Jackson Joseraj The JVM Architecture Explainedより引用
上記のアーキテクチャ図に示すように、JVMは3つの主要なサブシステムに分かれています。
クラスローダーサブシステムは、クラスのバイナリーファイル(*.class)をロード、リンク、初期化します。
クラスは、クラスローダーサブシステムのロードコンポーネントからロードされます。
ロードコンポーネントは、3つのクラスローダから構成されています。
優先順 | 名称 | 説明 |
---|---|---|
1 | ブートストラップクラスローダ (Boot Strap ClassLoader) |
ブートストラップクラスパスに指定されたクラスをロードするためのクラスローダです。 JavaAPI(%JRE_HOME%/lib/rt.jar)をロードする責務があるクラスローダです。 |
2 | エクステンションクラスローダ (Extension ClassLoader) |
%JRE_HOME%/lib/extディレクトリに格納された JREのオプションライブラリをロードするためのクラスローダです。 |
3 | アプリケーションクラスローダ (Application ClassLoader) |
クラスパス(環境変数[classpath]又はjava起動オプション-cp指定)に指定した クラスファイルをロードするためのクラスローダです。 |
リンクのプロセスは、クラス及びインターフェイスを取得し、
そのクラスがJVM内で実行できる状態にするためにクラス間を紐づけます。
これは、何を意味しているかといえば、クラスの継承やインターフェイスの実装で、
extends,implementsしている直接の親クラス、更にその親クラスやインターフェイスを
検証及び準備するこを指します。
リンクには、以下3つのプロセスからリンクを行います。
実行順 | 名称 | 説明 |
---|---|---|
1 | 検証 (Verify) |
生成されたバイトコードが適切であるかどうかを検証します。 検証が失敗した場合、検証エラーが発生します。 |
2 | 準備 (Prepare) |
全てのstatic変数(クラス変数や固定値)に対して、メモリを割り当てます。 メモリをデフォルト値に初期化します。 |
3 | 解決 (Resolution) |
ランタイムデータ領域(RunTime Data Areas)に定義されるメソッド領域(クラス情報の格納先)の
(Run-Time Constant Pool)という領域に、定数の文字、数字、クラス、フィールド、型への参照情報を保持しています。 クラスに定義されている全ての型をメモリの参照情報からRun-Time Constant Poolで管理する独自の参照情報へ変換します。 |
クラスに定義された全てのstatic変数(クラスフィールド)と宣言されている値の紐づけを行います。
staticブロックで記述されているロジックを実行します。
実行時データ領域とは、プログラムでインスタンスの生成や、
メソッドの実行時などに必要な情報をメモリに管理するための領域です。
実行時データ領域は、役割分担するために大きく5つの領域で管理されます。
メソッド領域とは、プログラムで利用されるクラス毎にクラスの情報を管理される領域です。
クラス情報から生成されたインスタンスは、ヒープ領域に格納されます。
クラス情報には、クラスのコンストラクタ、フィールド、メソッド情報が含まれます。
実行されるクラスと参照先のクラスの情報毎に上記図の丸(クラス情報を意味)が増えていきます。
また、クラス毎にクラスに定義されている全ての型の参照情報を管理するRun-Time Constant Poolの領域を確保します。
メソッド領域は、共有リソースで各スレッドからアクセス可能です。
ヒープ領域は、インスタンスとインスタンスのメンバーフィールドを管理する領域です。
ヒープ領域には、StringPoolという領域が存在し、private String msg = "hello"
と記載した場合に
"hello"という固定値が管理されます。 private String msg = new String("hello")
と記載した場合には、
ヒープ領域にStringのインスタンスとして、領域が確保され、StringPoolには保存されません。
ヒープ領域は共有リソースで各スレッドからアクセス可能です。即ちスレッドセーフではありません。
JVMスタック領域は、スレッド(Thread)毎の領域です。
そして、オブジェクトの各メソッド実行毎にJVMスタック領域にスタックフレームを作成します。
実行したメソッドが正常終了したと同時に生成されたスタックフレームは解放されます。
すべてのローカル変数がスタックメモリに作成されます。スタック領域は共有リソースではないため、スレッドセーフです。
スタックフレームは3つのサブエンティティに分割されています。
一般的なコンピュータアーキテクチャの用語では、プログラムカウンタ(PC)レジスタは、いつでも実行されている現在の命令を追跡します。
これは、プログラム中の命令のシーケンスにおける現在の命令へのポインタのようなものです。Java JVMの用語でも同じです。
新しいスレッドが作成されるたびにプログラムカウンタ(PC)レジスタが作成されます。
PCはスレッド内で実行されている現在のステートメントへのポインタを保持します。
現在実行中のメソッドが 'native'の場合、プログラムカウンタレジスタの値は不定になります。
いうなればスレッドが実行するJavaの構文によって、OSに対してOSへの命令が実行されています。その命令を管理しています。
Java仮想マシンの実装では、ネイティブメソッド(Javaプログラミング言語以外の言語で記述されたメソッド)をサポートするために、
2.5.6. Native Method Stacksより引用
「Cスタック」と呼ばれる従来のスタックを使用できます。
ネイティブ・メソッド・スタックは、Java仮想マシンの命令セットのインタープリタをC言語などの言語で実装することによっても使用できます。
ネイティブ・メソッドをロードできず、従来のスタックに依存しないJava Virtual Machineの実装では、ネイティブ・メソッドを提供する必要はありません
実行時データ領域に割り当てられたバイトコードは、実行エンジンによって実行されます。
実行エンジンは、バイトコードを読み取り、それを1つずつ実行します。
インタプリタは、バイトコードをより速く解釈しますが、ゆっくりと実行します。
インタプリタの欠点は、新しい解釈が必要となるたびに、1つのメソッドが複数回呼び出される場合です。
JITコンパイラはインタプリタの不利な点を打ち消します。
実行エンジンはインタープリタの助けを借りてバイトコードの変換を行いますが、
繰り返しコードが見つかると、バイトコード全体をコンパイルしてネイティブコードに変更するJITコンパイラを使用します。
このネイティブコードは、システムのパフォーマンスを向上させる繰り返しメソッド呼び出しに直接使用されます。
GCは、javaコマンド起動時で指定したヒープ領域のサイズ又はデフォルトのサイズ内の上限に近く、
空き領域が少なくなった際にヒープ領域のインスタンス情報の内、参照されなくなったオブジェクトの情報を消去します。
大規模システムを管理する立場のエンジニアの場合には、サーバのハードウェアのスペック、同時アクセス数など多角的な情報から、 JavaコマンドのHeap領域の上下限の値を確定し、OOME(OutOfMemoryError)などが発生しないように調整します。
JNIはネイティブメソッドライブラリと対話し、実行エンジンに必要なネイティブライブラリを提供します。
実行エンジンに必要なネイティブライブラリのコレクションです。
本頁を纏めるにあたって、参考にしたサイトになります。