ぺーぺーSEのブログ

備忘録・メモ用サイト。

JavaVMのメモリ管理に関するまとめ(Java8版)

大変長らく放置していた下記サイトをJava8以降、つまりMetaspaceが導入されてからのJVM、HotRockitの情報へ書き直す。

blog.pepese.com

一気に書き直すのは大変なので、随時更新。するかもしれない。

HotRockitのメモリ領域について

f:id:tanakakns:20161010144151p:plain

領域 説明
Javaヒープ HotSpotVM上で起動するJavaプログラムのリソースを管理する領域。New領域とOld領域で構成される。
-> New 新規オブジェクトと閾値(-XX:MaxTenuringThreshold)未満のオブジェクトが配置される。Youngとも呼ばれる。
-> -> Eden 新規のオブジェクトが配置される。
-> -> From CopyGC(ScavengeGC、マイナーGC)が実行された際に、使用中のオブジェクトはここへコピーされる。Survivorとも呼ばれる。
-> -> To CopyGC(ScavengeGC、マイナーGC)が実行された際に、使用中のオブジェクトはここへコピーされる。Survivorとも呼ばれる。
-> Old New領域で閾値(-XX:MaxTenuringThreshold)を超えたオブジェクトが配置される。
Nativeメモリ JavaヒープがJVMにより管理されるのに対し、Native目盛はOSが管理する領域。
-> Metaspace クラスやメソッドの情報が格納される領域。
-> Cヒープ ネイティブ領域。JavaVM自身のリソースを管理する領域。NativeMethod、Javaスレッド自身、(8k以上の)バッファ領域として利用される。
-> スレッドスタック スレッド毎に保持するスタックの領域。命令の順序をスタックする。

Javaヒープ領域はJVMによって管理された領域であるのに対して、NativeメモリはOSが管理する直のメモリ領域の事を指す。

  • 「Javaヒープ」=「New領域」+「Old」領域
    • 「New領域」=「Eden」+「From (Survivor0)」+「To (Survivor1)」
  • 「Nativeメモリ」=「Metaspace」+「Cヒープ」+「スレッドスタック」

各領域を確保するVM引数

引数 説明
-Xms Javaヒープ全体の初期値。
-Xmx Javaヒープ全体の最大値。
-XX:NewSize New領域の初期値。(-Xmnと同じ)
-XX:MaxNewSize New領域の最大値。
-XX:SurvivorRatio EdenとFrom/To(Survivor)の割合を設定。EdenのサイズをFromまたはTo領域のサイズで割った値。(FromとTo領域は同じサイズ)
-XX:MetaspaceSize Metaspaceの初期値。
-XX:MaxMetaspaceSize Metaspaceの最大値。
-Xss スレッドスタックの領域(-XX:ThreadStackSizeと同じ)

Oldの領域は「Javaヒープ」-「New領域」となる。
スレッドスタックの領域はプラットフォームによりその初期値が異なる。
また、Cヒープのサイズ設定はできない。

http://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

PermanentとMetaspace

従来のPermanent領域はクラスやメソッドのメタ情報の他、staticな変数や定数等も領域内に格納していた。 が、Metaspaceではクラス・メソッドの情報のみ保持し、その他の情報はJavaヒープ上の保持に変更された。 GCに変更点は無い。MetaspaceがNativeメモリ上に存在していたとしてもFull GC時のコレクション対象となる。

Metaspaceのサイズ

Metaspace領域でのメモリ使用量は概ね、ロードされるクラスファイルの合計サイズになる。
JavaEEサーバの場合、次に示すクラスファイルのサイズの総和から見積もることができる。

  1. WEB-INF/classes以下のすべてのクラスファイル
  2. WEB-INF/lib以下のJARファイルに含まれる,すべてのクラスファイル
  3. JSPコンパイル結果として生成された,すべてのクラスファイル
  4. EJB-JARに含まれるすべてのクラスファイル
  5. コンテナ拡張ライブラリ,ライブラリJAR,参照ライブラリを利用している場合に追加するJARファイルに含まれる,すべてのクラスファイル
  6. コンテナが作成するクラスファイル
    • アプリケーション開始後のMetaspace領域-アプリケーション登録前のMetaspace領域。実際にJ2EEサーバを起動し,Metaspace領域を確認して算出することが望ましい
  7. JavaEEサーバ提供のクラスファイル(システムクラスファイル)
  8. JDK提供のクラスファイル

OutOfMemoryErrorが発生するタイミング

以降「溢れる」という表現を使うが、これは「GCしても解放されない」「残り、もしくは全体のメモリより大きなオブジェクトを生成する」ことによってメモリあるいは特定の領域が不足することを指す。

  • New領域が溢れた場合
    • New領域のオブジェクトはある程度の期間存在し続けるとOld領域へ移動される。しかし、移動する条件に達する前のオブジェクトで溢れる場合がある。
      • ※JavaVMはNew領域が溢れそうな際ある程度New領域を広げる処理を行っているようだが、それでも溢れた場合はOutOfMemoryErrorとなる。
  • Old領域が溢れた場合
    • 参照されつづけるオブジェクトが大量に存在する場合に溢れる。
  • Metaspaceが溢れた場合
    • クラス・メソッドをロードするMetaspace領域が不足する場合に発生する。
    • 前述の「Metaspaceのサイズ」を参照して適切なサイズとする。
  • Cヒープが溢れた場合
    • Javaのスレッドが大量に作成された場合に溢れる。
    • Cヒープが溢れてOutOfMemorryErrorが発生した場合、スタックトレースの先頭が「Native Method」である。
    • スレッド数はOSのパラメタで設定されており、それが大きな値で設定されている場合に発生する。
    • Linuxの場合、「cat /proc/sys/kernel/threads-max」で確認することができ、「/etc/sysctl.conf」ファイルの「kernel.threads-max」で設定される。
    • Linuxで起動中のJavaプロセスのスレッド数を確認する方法に「ps -efL | grep -e java | grep -v grep | wc -l」(psにLオプションを付ける)等がある。「NLWP」がスレッド数。
  • その他、参考

GC(ガベッジコレクション)

GCはメモリ上のオブジェクトをお掃除してくれる仕組み。

GCが発生するタイミング

  • ヒープメモリ中に新規オブジェクトを作成するために必要な空き領域が足りなくなったとき
  • プログラム中でSystem.gc()が実行されたとき
    • 「-XX:+DisableExplicitGC」を指定することにより、無効化することができる
  • JVMで実行する処理がなくなってアイドル状態になったとき
  • 下記オプションで設定された定期的なGC(Java RMI使用時)

GCの動き

  1. Eden領域が一杯になるとCopyGC(ScavengeGC、マイナーGC)が実行される。このとき、まだ使用中のオブジェクトはEden領域からTo領域へ移動され、使用されていないオブジェクトは削除される。
  2. Eden領域が一杯になるとCopyGC(ScavengeGC、マイナーGC)が実行され、今度はEden・To領域で使用中のオブジェクトはFrom領域へコピーされ、使用されていないオブジェクトは削除される。
  3. Eden領域が一杯になるとCopyGC(ScavengeGC、マイナーGC)が実行され、今度はEden・From領域で使用中のオブジェクトはTo領域へコピーされ、使用されていないオブジェクトは削除される。
  4. 2、3が繰り返される。この間、CopyGC(ScavengeGC、マイナーGC)によってFrom⇔To間でコピーされたオブジェクトはコピー回数がカウントされ、閾値を超えるとOld領域へコピーされる。
  5. コピー回数の閾値は「-XX:MaxTenuringThreshold」で設定する。 ※Survivor(From、To)領域があふれてもOld領域にオブジェクトが移動する。

参考:Javaはどのように動くのか0図解でわかるJVMの仕組み

GCの動きをコントロールするオプション

オプション 説明
-XX:MaxTenuringThreshold New領域において、オブジェクトがマイナーGCを何回超えて生き残ると、Old領域に移動するかのしきい値の最大値(実際のしきい値は、1からMaxTenuringThreshold(15)の範囲で動的に決定される)
-XX:TargetSurvivorRatio Survivor領域がいっぱいと判断される使用率
-XX:+DisableExplicitGC プログラム中で強制的にFull GCを起こすSystem.gc()を処理を無効化する

GCの種類

GCには以下の種類がある。

  • シリアルGC
  • パラレルGC
  • コンカレントGC
  • G1GC

以下のオプションでGCを選択する。

オプション 説明
-XX:+UseSerialGC New領域、Old領域ともに、GCを単一スレッドで実行。シングルコアCPUで使用する。
-XX:+UseParallelGC New領域では、GCを複数スレッドで実行。Old領域が不足したら、Full GCを単一スレッドで実行。マルチコアCPUで使用する。
-XX:+UseParallelGC -XX:+UseParallelOldGC New領域では、GCを複数スレッドで実行。Old領域が不足したら、Full GCを複数スレッドで実行。マルチコアCPUで使用する。
-XX:+UseConcMarkSweepGC New領域は-XX:+UseParallelGCと同様の処理。Old領域でCMS GC(コンカレントGC)を実行。CMS GCでもメモリが不足する場合は、Full GCを実行。
-XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:+UseConcMarkSweepGCをインクリメンタル・モードで実行。
-XX:+UseG1GC Javaヒープを「リージョン」という領域に細分化し、New領域やOld領域などを割り当てる方式。

通常のエンタープライズシステムであればコンカレントGCで十分。
しかし、Javaヒープが数十GBともなるとG1GCの検討が必要。 G1CGでは参考のオプションも追加して設定が必要。(詳細未調査)