ここでは、Javaヒープ、メタスペースとガーベジコレクション(GC)を説明します。
Javaヒープは、Javaプロセス内に存在するJavaオブジェクトを格納するための領域です。
Javaヒープは、New世代領域、Old世代領域に大別され、Java VMが管理・制御します。
JDK/JRE 7以前でJavaヒープに存在していた、Permanent世代領域は、JDK/JRE 8では廃止されました。Permanent世代領域の代替として、メタスペースが導入されました。
Java VMは、Javaアプリケーションの実行時に、JavaオブジェクトをJavaヒープの各領域に格納します。Javaヒープの空き容量がなくなった場合は、java.lang.OutOfMemoryErrorがスローされます。
また、不要となったJavaオブジェクトはGCにより回収され、Javaヒープの空き領域が増加します。なお、New世代領域に存在する不要となったJavaオブジェクトを回収するGC処理を「NewGC処理(またはマイナーGC処理)」といいます(NewGCまたはマイナーGCと略す場合もあります)。また、New世代領域だけでなく、Old世代領域を含むJavaヒープ全体に存在する不要となったJavaオブジェクトを回収するGC処理を「FullGC処理」といいます(FullGCと略す場合もあります)。
New世代領域とOld世代領域(メモリ割り当てプール)
New世代領域とOld世代領域は、インスタンスや配列などのJavaオブジェクトを管理する領域です。
New世代領域は寿命の短いJavaオブジェクトを管理します。通常、Javaアプリケーションで要求されたオブジェクトは、New世代領域に生成されます。一定期間New世代領域で生存したJavaオブジェクトは、GC処理によってOld世代領域に移動されます。そして、Old世代領域に存在する不要となったJavaオブジェクトは、FullGC処理によって回収されます。領域がNew世代とOld世代に分かれるのは、世代別にGC処理を実行するためです。
なお、メモリ割り当てプール全体のサイズの初期値および最大値は、それぞれ、“-Xms”オプションおよび“-Xmx”オプションで指定することができます。
メタスペース
メタスペースは、Javaのクラス、メソッドなど、永続的に参照される静的オブジェクトを管理する領域です。
メタスペースはJavaヒープには存在せず、OSが管理するネイティブメモリ上の領域に取られます。そのため、OSが領域を獲得できれば上限はありません(以下で説明する“-XX:MaxMetaspaceSize”オプションでサイズを指定しない場合)。JDK/JRE 7のPermanent世代領域と比較して、OutOfMemoryErrorが発生しにくい、という特徴があります。
メタスペースは、クラスローダーごとに分けて管理しています。クラスローダーがFullGC処理によって回収されると、対応するメタスペースも削除されます。あるクラスローダーでロードされたクラス情報を別のクラスローダーが管理するメタスペースに格納できません。また、各クラスローダーのメタスペースは、クラス情報ごとに取得するのではなく、ある程度のまとまったサイズごとに取得して、そこにクラス情報を設定していきます。
メタスペースの大きさの最大値は、“-XX:MaxMetaspaceSize”オプションで指定することができます。オプションを指定しなかった場合、メタスペースはメモリ資源のある限り無制限です。しかし、複数のJavaプロセスを同時に起動した場合に、全体で使用するメモリ量をチューニングする場合は、本オプションを指定することを推奨します。
“-XX:MetaspaceSize”オプションは、初めて超えたときにFullGCを発生させるしきい値となるサイズを指定します。Javaプロセスの起動時に確保する初期サイズではありません。
JDK/JRE 7のPermanent世代領域に関連するオプションが指定された場合は無視されます。
注意
Compressed Class Space
64ビットモードのJDK/JRE 8では、通常のメタスペースの他に、クラス情報の一部を格納するCompressed Class Space と呼ばれる領域を使用します。この領域は、Java VM起動時に1GBのサイズだけリザーブされます。
参考
ユーザ空間
Javaヒープは、Javaプロセスのユーザ空間内に割り当てられます。
また指定された最大値までJavaヒープが利用できる環境にするため、Java VM起動時に、メモリ割り当てプールを、最大値の大きさで連続領域としてリザーブします(同一プロセス内の他の処理から使用できないようにします)。
このため、Javaヒープの最大値を大きく設定すると、その分だけスタックなど他の処理に割り当てられるメモリ領域が減少します。
別な言い方をすれば、ユーザ空間内で使用できるメモリ量の上限から、Javaアプリケーション自身、Java VM、そしてネイティブモジュール(OSを含む)が使用するメモリ量などを差し引いた値が、Javaヒープとして使用できる上限となります。
なおJava VM起動時に指定されたJavaヒープの大きさが利用できない場合、Java VMは以下のメッセージを出力し、Javaプロセスを終了させます。
Error occurred during initialization of VM |
このメッセージが出力された場合は、Javaヒープを小さくするチューニングを行ってください。
またSolaris用およびLinux用のJava VMにおいて、Java VM起動時またはJavaアプリケーション実行中に「ユーザ空間不足」または「仮想メモリ不足」が発生した場合、Java VMは以下のメッセージを出力し、Javaプロセスを終了させます。
Java VM起動時の場合
制御名: mmap failed: errno=エラー情報, 制御情報.... |
Javaアプリケーション実行中の場合
制御名: mmap failed: errno=エラー情報, 制御情報.... |
メモリ不足が発生した際のJava VMの制御名
メモリ不足が発生した際のJava VMのエラー情報
メモリ不足が発生した際のJava VMの制御情報
ユーザ空間が不足している場合は、Javaヒープを小さくするチューニングを行ってください。
仮想メモリが不足している場合は、他の不要なプロセスを終了して仮想メモリに余裕を持たせるか、物理メモリ(RAM)またはスワップファイルを拡張して仮想メモリを増やすようにチューニングを行ってください。
参考
Javaヒープにメモリを割り当てるタイミング
Java VMは、OSの仮想メモリ資源を効率的に利用するために、起動時にJavaヒープの各領域の初期値を割り当て、各領域の最大値まで段階的に拡大する制御方法を用いています。
具体的には、Javaプロセスの起動時はメモリ割り当てプールの各初期値のサイズを割り当てます。その後、各領域のサイズは各領域の最大値までの範囲内で、GC処理後や、オブジェクトの生成時のタイミングで、その時々の最適な値に自動調整されます(GC処理としてメモリ割り当てプールの最小使用量保証機能を無効にしたパラレルGCを使用している場合は、メモリ割り当てプールの大きさが、初期値よりも小さくなる場合があります)。
参考
スワップ発生時の対処方法
Javaヒープの各領域を拡大していく過程で、OS側で物理メモリの資源をディスクにスワップする処理が発生する場合があります。このスワップ処理により、各領域を拡大するFullGC処理に時間がかかる場合があります。FullGC処理中のスワップ処理発生による時間遅延が問題になる場合は、物理メモリを増やす、またはJavaヒープを小さくするチューニングを行ってください。
パラレルGCについては、“10.2.3 New世代領域用制御処理並列化機能付きGC(パラレルGC)”を参照してください。