JVM(Java Virtual Machine)とは
- JVMとはJava Virtual Machineの略で、Javaのプログラムを動かすために必要なソフトウェアです。
- Java bite codeを実行できる主体です。
CPUやOSの種類と関係なく実行できます。
つまり、OS上で動くプロセスで、Javaコードをコンパイルして得られたバイトコードを該当OS(Windows、OS X、Linuxなど)が理解(解析)できる機械語に変換し、実行してくれます。
様々なOS用のJVM
JVMの構成
大きく見てみると以下の4つです。
- Class Loader
- Execution Engine
- Garbage Collector
- Runtime Data Area
JVMの構成
Class Loader
Javaではソースコードを作成するとTest.javaのように.javaファイルが生成されます。
javaソースをJavaコンパイラがコンパイルを行うとTest.classのように.classファイル(バイトコード)が生成されます。
このように生成されたClassファイルを組んでJVMがOSから与えられたメモリ領域であるRuntime Data Areaに積載を行う役割をClass Loaderがします。
Execution Engine
- Execution EngineはClass Loaderによって積載されたクラス(バイトコード)を機械語に変換し命令語の単位で実行させます。
- 命令語を一つ一つ実行するインタープリター式とJIT(Just-In-Time)コンパイラを利用する方法があります。
※JITコンパイラは適切な時間に全体のバイトコードをネイティブコードに変更してExecution Engineがネイティブにコンパイルされたコードを実行することで性能を高める方法です。
Garbage Collector
- Garbage Collector(GC)はHeapメモリ領域に生成(積載)されたオブジェクトの中で参照されていないオブジェクトの探索を行い、削除します。
- GCが行われる時間は正確にいつなのかわかりません。 (参照がなくなってすぐ解除するとは保証できないため)
- GCが行われる間はGCを行うスレッド以外のスレッドは止まります。
※Full GCが行われる数秒間はすべてのスレッドが停止したら障害にもつながる致命的な問題が生じます。
Runtime Data Area
- JVMのメモリ領域でJavaアプリケーションを実行する際、使われるデータの積載を行う領域です。
- Method Area, Heap Area, Stack Area, PC Register, Native Method Stackに分けられています。(代表的に他にもあります。)
Runtime Data Areaの構造
Method area
- メソッド領域とは、プログラムで利用されるクラス毎にクラスの情報を管理される領域です。
- クラスメンバーの変数名、データタイプ、アクセス制御子の情報のようなフィールド情報とメソッド名、戻り値の型、パラメータ、Type情報(Interfaceかclassか)、Constant Pool、static変数、final class変数などが管理されます。
Heap area
-
ヒープ領域は、インスタンスとインスタンスのメンバーフィールドを管理する領域です。
- newキーワードで生成されたオブジェクトと配列を管理する領域です。
-
メソッド領域にロードされているクラスのみ、生成が可能でGarbage Collectorが参照されていないメモリをスキャンし解除を行う領域です。
Stack area
- スレッド(Thread)毎の領域です。
- ローカル変数、パラメータ、戻り値、演算に使われる任意の値などを管理する領域です。
- スタック領域は共有リソースではないため、スレッドセーフです。
- スタックフレームは3つのサブエンティティに分割されています。
- ローカル変数配列(Local Variable Array) メソッドに関連するローカル変数の数と、対応する値が格納されます。
- オペランドスタック(Operand stack) 実行するために中間のオペレーションが必要な場合、オペランドスタックは オペレーションを実行するためにランタイムワークスペース(作業領域)として機能します。
- フレームデータ(FrameData) メソッドに対応するすべてのシンボルがここに格納されます。 例外の場合、キャッチブロック情報はフレームデータ内で維持される。
PC Register
- Thread(スレッド)が生成されるたびに生成される領域でProgram Counter、つまりスレッド内で実行されている現在のステートメントへのポインタを保持します。 (*CPUのレジスタとは違う)
- 現在実行中のメソッドが 'native'の場合、プログラムカウンタレジスタの値は不定になります。
Native method stack
- Java以外のプログラミング言語で作成されたネイティブコードのためのメモリ領域です。
- 普通はC/C++などのコードを使用するためのスタックです。(JNI)
※JNI:JNIはネイティブメソッドライブラリと対話し、実行エンジンに必要なネイティブライブラリを提供します。
スレッドが作成された時
メソッド領域とヒープ領域をすべてのスレッドが共有し、
スタック領域とPCレジスタ、ネイティブメソッドスタックはそれぞれスレッドごとに作成され、共有はされないです。
Heap area & Garbage Collector
この項目ではHeap areaはGCの重要対象なのでもうちょっと詳しく見ていきましょう。
(Stack領域とMethod領域もGCの対象になります。)
Heap areaは5つの領域(eden, survivor1, survivor2, old, permanent)となっています。
JDK7まではpermanent領域がheapに存在していました。JDK8からはpermanent領域はなくなり、その一部が"meta space領域"に変更されました。(上の図はJDK7基準です。) meta space領域はNative stack領域に含まれるようになりました。
(survivor領域の数字は意味がなく2つに分けられていることが重要です。)
Heap領域をわざわざ5つに分けた理由は効率的にGCを行わせるためです。
詳しいことはGCが行われるプロセスを見ながら説明していきたいと思います。
GCはMinor GCとMajor GCに分けられている
※Minor GC : New領域で行われるGC
- 最初にオブジェクトが生成されたらEden領域に生成されます。
- Eden領域にオブジェクトがいっぱいになると最初のGCが行われます。
- survivor1領域にEden領域のメモリがそのままコピーされます。そしてsurvivor1領域を除く他の領域のオブジェクトを削除します。
- Eden領域もsurvivor1領域もいっぱいになったらEden領域に生成されたオブジェクトとsurvivor1領域に生成されたオブジェクトの中で参照されているオブジェクトがあるかを検索します。
- 参照されていないオブジェクトはそのまま置いて、参照されているオブジェクトのみsurvivor2領域にコピーしていきます。
- survivor2領域を除く他の領域のオブジェクトを削除していきます。
- 上記の流れで一定の回数以上に参照されているオブジェクトはsurvivor2からOld領域に移動させます。
※上記の流れを繰り返し、survivor2領域までいっぱいになる前に続けてOldに移動させます。
※Major GC(Full GC) : Old領域で行われるGC
- Old領域にあるすべてのオブジェクトを検査し、参照されているかを確認します。
- 参照されていないオブジェクトは収集して一気に削除を行います。
※Minor GCより所要時間が長いし、GCが行われている間はGC以外の全てのスレッドは停止されます。
Major GC(Full GC)が行われたら??
Old領域の参照されていないオブジェクトをチェックし、該当オブジェクトは全部削除されます。
そうなるとHeapメモリ領域に削除されて空きメモリ空間ができますが、この空きメモリをなくすために再構成を行います。(メモリ整理)
なので、メモリを再構成している際に他のスレッドがメモリを使えないようにするため、すべてのスレッドが停止されることです。