はじめに

本章では、JavaのスレッドのAPIを学習する前に、Javaエンジニアにとって非常に重要なJVMのアーキテクチャについて説明します。
本章の内容は、Javaにおける学習において経験年数に関わらず、抑えておきたい内容です。
JVMのアーキテクチャを理解している人のスレッド操作は、まずバグの発生率が低く、
バグや何かしらのJVMにおける問題が発生したときの問題解決能力が非常に高いです。

Java8ベースのJVMのアーキテクチャについて説明します。何度も本頁及び他の参考文献を読み理解に心がけて下さい。

本頁に関しては、Javaを初めての方には、少し理解が難しい内容ばかりです。
ただ、Javaを長く利用される方は、数年掛けても良いので本頁及び参考サイトを参考にして、
JVMの概要については、理解して下さい。まずは、概要図を流し見して下さい。

JVMのアーキテクチャについて理解は、Javaエンジニアとして1流になるためは必須の知識です。

JVM アーキテクチャ図

JVMアーキテクチャ図 ©Jackson Joseraj The JVM Architecture Explainedより引用

上記のアーキテクチャ図に示すように、JVMは3つの主要なサブシステムに分かれています。

  1. クラスローダーサブシステム(Class Loader SubSystem)
  2. ランタイムデータエリア(Rumtime Data Area)
  3. 実行エンジン(Execution Engine)

1.クラスローダーサブシステム

クラスローダーサブシステムは、クラスのバイナリーファイル(*.class)をロード、リンク、初期化します。

1.1.ロード(loading)

クラスは、クラスローダーサブシステムのロードコンポーネントからロードされます。
ロードコンポーネントは、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指定)に指定した
クラスファイルをロードするためのクラスローダです。

1.2.リンク(linking)

リンクのプロセスは、クラス及びインターフェイスを取得し、
そのクラスがJVM内で実行できる状態にするためにクラス間を紐づけます。

これは、何を意味しているかといえば、クラスの継承やインターフェイスの実装で、
extends,implementsしている直接の親クラス、更にその親クラスやインターフェイスを 検証及び準備するこを指します。

リンクには、以下3つのプロセスからリンクを行います。


実行順 名称 説明
1 検証
(Verify)
生成されたバイトコードが適切であるかどうかを検証します。
検証が失敗した場合、検証エラーが発生します。
2 準備
(Prepare)
全てのstatic変数(クラス変数や固定値)に対して、メモリを割り当てます。
メモリをデフォルト値に初期化します。
3 解決
(Resolution)
ランタイムデータ領域(RunTime Data Areas)に定義されるメソッド領域(クラス情報の格納先)の (Run-Time Constant Pool)という領域に、定数の文字、数字、クラス、フィールド、型への参照情報を保持しています。
クラスに定義されている全ての型をメモリの参照情報からRun-Time Constant Poolで管理する独自の参照情報へ変換します。

1.3.初期化

クラスに定義された全てのstatic変数(クラスフィールド)と宣言されている値の紐づけを行います。
staticブロックで記述されているロジックを実行します。

2.実行時データ領域(run-time data areas)

実行時データ領域とは、プログラムでインスタンスの生成や、
メソッドの実行時などに必要な情報をメモリに管理するための領域です。
実行時データ領域は、役割分担するために大きく5つの領域で管理されます。

2-1.メソッド領域( method area)

メソッド領域

メソッド領域とは、プログラムで利用されるクラス毎にクラスの情報を管理される領域です。
クラス情報から生成されたインスタンスは、ヒープ領域に格納されます。
クラス情報には、クラスのコンストラクタ、フィールド、メソッド情報が含まれます。
実行されるクラスと参照先のクラスの情報毎に上記図の丸(クラス情報を意味)が増えていきます。
また、クラス毎にクラスに定義されている全ての型の参照情報を管理するRun-Time Constant Poolの領域を確保します。

メソッド領域は、共有リソースで各スレッドからアクセス可能です。

2-2.ヒープ領域( heap area )

ヒープ領域

ヒープ領域は、インスタンスとインスタンスのメンバーフィールドを管理する領域です。
ヒープ領域には、StringPoolという領域が存在し、private String msg = "hello"と記載した場合に
"hello"という固定値が管理されます。 private String msg = new String("hello")と記載した場合には、
ヒープ領域にStringのインスタンスとして、領域が確保され、StringPoolには保存されません。

ヒープ領域は共有リソースで各スレッドからアクセス可能です。即ちスレッドセーフではありません。

2-3.JVM Stack Area

スタック領域

JVMスタック領域は、スレッド(Thread)毎の領域です。
そして、オブジェクトの各メソッド実行毎にJVMスタック領域にスタックフレームを作成します。
実行したメソッドが正常終了したと同時に生成されたスタックフレームは解放されます。

すべてのローカル変数がスタックメモリに作成されます。スタック領域は共有リソースではないため、スレッドセーフです。

スタックフレームは3つのサブエンティティに分割されています。

  1. ローカル変数配列(Local Variable Array) メソッドに関連するローカル変数の数と、対応する値が格納されます。
  2. オペランドスタック(Operand stack) 実行するために中間のオペレーションが必要な場合、オペランドスタックは  オペレーションを実行するためにランタイムワークスペース(作業領域)として機能します。
  3. フレームデータ(FrameData) メソッドに対応するすべてのシンボルがここに格納されます。 例外の場合、キャッチブロック情報はフレームデータ内で維持される。

2-4.PCレジスター(PC Registers)

一般的なコンピュータアーキテクチャの用語では、プログラムカウンタ(PC)レジスタは、いつでも実行されている現在の命令を追跡します。
これは、プログラム中の命令のシーケンスにおける現在の命令へのポインタのようなものです。Java JVMの用語でも同じです。
新しいスレッドが作成されるたびにプログラムカウンタ(PC)レジスタが作成されます。
PCはスレッド内で実行されている現在のステートメントへのポインタを保持します。
現在実行中のメソッドが 'native'の場合、プログラムカウンタレジスタの値は不定になります。

いうなればスレッドが実行するJavaの構文によって、OSに対してOSへの命令が実行されています。その命令を管理しています。

2-5.ネイティブメソッドスタック(Native Method stacks)

Java仮想マシンの実装では、ネイティブメソッド(Javaプログラミング言語以外の言語で記述されたメソッド)をサポートするために、
「Cスタック」と呼ばれる従来のスタックを使用できます。
ネイティブ・メソッド・スタックは、Java仮想マシンの命令セットのインタープリタをC言語などの言語で実装することによっても使用できます。
ネイティブ・メソッドをロードできず、従来のスタックに依存しないJava Virtual Machineの実装では、ネイティブ・メソッドを提供する必要はありません

2.5.6. Native Method Stacksより引用

3.実行エンジン(Execution Engine)

実行時データ領域に割り当てられたバイトコードは、実行エンジンによって実行されます。
実行エンジンは、バイトコードを読み取り、それを1つずつ実行します。

3-1.インターセプタ(Interpreter)

インタプリタは、バイトコードをより速く解釈しますが、ゆっくりと実行します。
インタプリタの欠点は、新しい解釈が必要となるたびに、1つのメソッドが複数回呼び出される場合です。

3-2.JITコンパイラ(JIT Compiler)

JITコンパイラはインタプリタの不利な点を打ち消します。
実行エンジンはインタープリタの助けを借りてバイトコードの変換を行いますが、
繰り返しコードが見つかると、バイトコード全体をコンパイルしてネイティブコードに変更するJITコンパイラを使用します。
このネイティブコードは、システムのパフォーマンスを向上させる繰り返しメソッド呼び出しに直接使用されます。

4.GC(Garbage Collector)

GCは、javaコマンド起動時で指定したヒープ領域のサイズ又はデフォルトのサイズ内の上限に近く、
空き領域が少なくなった際にヒープ領域のインスタンス情報の内、参照されなくなったオブジェクトの情報を消去します。

大規模システムを管理する立場のエンジニアの場合には、サーバのハードウェアのスペック、同時アクセス数など多角的な情報から、 JavaコマンドのHeap領域の上下限の値を確定し、OOME(OutOfMemoryError)などが発生しないように調整します。

Java Native Interface (JNI): JNI

JNIはネイティブメソッドライブラリと対話し、実行エンジンに必要なネイティブライブラリを提供します。

Native Method Libraries

実行エンジンに必要なネイティブライブラリのコレクションです。

参考文献

本頁を纏めるにあたって、参考にしたサイトになります。

スレッド前の纏め

スレッドセーフなのは、メソッドのローカル変数のみ
ヒープ領域に格納されるインスタンス、インスタンスのメンバーフィールドは、
共有リソースでスレッドセーフではない
: