理解JVMメモリモデル、Javaメモリ管理は、Javaガベージコレクションの動作を理解するために非常に重要です。今日は、Javaにおけるメモリ管理、JVMメモリの異なる部分、およびガベージコレクションのチューニングを監視および実行する方法について調べてみましょう。
Java(JVM)メモリモデル
上記の画像に示されているように、JVMメモリは別々の部分に分割されています。広範なレベルで、JVMヒープメモリは物理的にYoung GenerationとOld Generationの2つに分割されます。
Javaにおけるメモリ管理 – Young Generation
若い世代はすべての新しいオブジェクトが作成される場所です。若い世代が満杯になると、ガベージコレクションが実行されます。このガベージコレクションはMinor GCと呼ばれます。 Young GenerationはEden Memoryと2つのSurvivor Memoryスペースに分割されます。Young Generationスペースに関する重要なポイント:
- ほとんどの新しく作成されたオブジェクトは、Edenメモリスペースに配置されています。
- Edenスペースがオブジェクトで満杯になると、マイナーGCが実行され、すべての生存オブジェクトがサバイバースペースの1つに移動されます。
- マイナーGCでは、サバイバーオブジェクトもチェックされ、他のサバイバースペースに移動されます。したがって、常に1つのサバイバースペースが空いています。
- 多くのGCサイクルを経て生き残ったオブジェクトは、古い世代のメモリスペースに移動されます。通常、若い世代のオブジェクトが古い世代に昇格する前の年齢の閾値を設定して行われます。
Javaにおけるメモリ管理 – 古い世代
古い世代のメモリには、長寿であり、多数のマイナーGCのラウンドを経て生き残ったオブジェクトが含まれています。通常、Old Generationメモリがいっぱいになると、ガベージコレクションが実行されます。Old GenerationのガベージコレクションはMajor GCと呼ばれ、通常は時間がかかります。
Stop the Worldイベント
すべてのガベージコレクションは「Stop the World」イベントです。すべてのアプリケーションスレッドが操作が完了するまで停止します。若い世代が短命のオブジェクトを保持しているため、マイナーガベージコレクションは非常に高速であり、アプリケーションに影響を与えません。しかし、メジャーガベージコレクションはすべての生存オブジェクトをチェックするため、時間がかかります。メジャーガベージコレクションはアプリケーションをガベージコレクションの期間中に応答不能にするため、最小限に抑える必要があります。応答性の高いアプリケーションでメジャーガベージコレクションが頻繁に発生する場合、タイムアウトエラーが発生することがあります。ガベージコレクターにかかる時間は、ガベージコレクションのストラテジーによって異なります。そのため、応答性の高いアプリケーションでタイムアウトを回避するためには、ガベージコレクターをモニターおよび調整する必要があります。
Javaメモリモデル – 恒久世代
恒久世代または「Perm Gen」には、アプリケーションが使用するクラスやメソッドを記述するためにJVMが必要とするアプリケーションメタデータが含まれています。Perm GenはJavaヒープメモリの一部ではないことに注意してください。Perm Genは、アプリケーションによって使用されるクラスに基づいてランタイム時にJVMによって設定されます。Perm GenにはJava SEライブラリのクラスとメソッドも含まれています。Perm Genオブジェクトは完全なガベージコレクションでガベージコレクトされます。
Javaメモリモデル – メソッド領域
メソッド領域はPerm Gen内のスペースの一部であり、クラス構造(ランタイム定数および静的変数)とメソッドおよびコンストラクタのコードを格納するために使用されます。
Javaメモリモデル – メモリプール
メモリプールは、実装がサポートしている場合に不変オブジェクトのプールを作成するためにJVMメモリマネージャによって作成されます。文字列プールはこの種のメモリプールの良い例です。メモリプールは、JVMメモリマネージャの実装によってHeapまたはPerm Genに属することがあります。
Javaメモリモデル – ランタイム定数プール
ランタイム定数プールは、クラスの定数プールのパーフォーマンスごとのランタイム表現です。クラスのランタイム定数および静的メソッドを含みます。ランタイム定数プールはメソッド領域の一部です。
Javaメモリモデル – Javaスタックメモリ
Javaスタックメモリはスレッドの実行に使用されます。それらには一時的なメソッド固有の値と、メソッドから参照されるヒープ内の他のオブジェクトへの参照が含まれています。スタックメモリとヒープメモリの違いをお読みください。
Javaのメモリ管理 – Javaヒープメモリスイッチ
Javaは、メモリサイズとその比率を設定するために使用できる多くのメモリスイッチを提供しています。一般的に使用されるメモリスイッチのいくつかは次のとおりです:
VM Switch | VM Switch Description |
---|---|
-Xms | For setting the initial heap size when JVM starts |
-Xmx | For setting the maximum heap size. |
-Xmn | For setting the size of the Young Generation, rest of the space goes for Old Generation. |
-XX:PermGen | For setting the initial size of the Permanent Generation memory |
-XX:MaxPermGen | For setting the maximum size of Perm Gen |
-XX:SurvivorRatio | For providing ratio of Eden space and Survivor Space, for example if Young Generation size is 10m and VM switch is -XX:SurvivorRatio=2 then 5m will be reserved for Eden Space and 2.5m each for both the Survivor spaces. The default value is 8. |
-XX:NewRatio | For providing ratio of old/new generation sizes. The default value is 2. |
ほとんどの場合、上記のオプションで十分ですが、他のオプションもチェックしたい場合は、JVMオプション公式ページをご確認ください。
Javaのメモリ管理 – Java Garbage Collection
Java Garbage Collectionは、メモリ内の未使用のオブジェクトを特定して削除し、将来の処理で作成されるオブジェクトに割り当てるための空間を解放するプロセスです。Javaプログラミング言語の最大の特徴の1つは、他のプログラミング言語(例:C)とは異なり、メモリの割り当てと解放が手動のプロセスではなく、自動ガベージコレクションされることです。 ガベージコレクターはバックグラウンドで実行されるプログラムであり、メモリ内のすべてのオブジェクトを調べ、プログラムのいずれの部分にも参照されていないオブジェクトを特定します。これらの参照されていないオブジェクトはすべて削除され、他のオブジェクトに割り当てるための空間が回収されます。ガベージコレクションの基本的な方法の1つには、次の3つのステップがあります:
- マーキング:これは、ガベージコレクターがどのオブジェクトが使用中であり、どのオブジェクトが未使用であるかを特定する最初のステップです。
- 通常の削除:ガベージコレクターは未使用のオブジェクトを削除し、他のオブジェクトに割り当てるための空きスペースを回収します。
- 圧縮を伴う削除:性能向上のために、未使用のオブジェクトを削除した後、生き残ったすべてのオブジェクトを一緒に移動できます。これにより、新しいオブジェクトへのメモリの割り当ての性能が向上します。
単純なマークと削除アプローチには2つの問題があります。
- 最初の問題は、ほとんどの新しく作成されたオブジェクトが使用されなくなるために効率的ではないことです。
- 次に、複数のガベージコレクションサイクルで使用されているオブジェクトは、将来のサイクルでも使用される可能性が高いです。
上記の単純なアプローチの欠点が、Java Garbage Collectionが世代的である理由であり、ヒープメモリにはYoung GenerationとOld Generationのスペースがあります。すでに、マイナーGCとメジャーGCに基づいてオブジェクトがどの世代のスペースから別の世代のスペースにスキャンおよび移動されるかを説明しました。
Javaでのメモリ管理 – Java Garbage Collectionのタイプ
アプリケーションで使用できるガベージコレクションのタイプは5つあります。アプリケーションでガベージコレクション戦略を有効にするためにJVMスイッチを使用するだけです。それらを1つずつ見てみましょう。
- Serial GC(-XX:+UseSerialGC):シリアルGCは、マイナーGCおよびメジャーGC、つまりYoung GenerationおよびOld Generationのガベージコレクションにシンプルなマークスイープコンパクトアプローチを使用します。シリアルGCは、単純なスタンドアロンアプリケーションやCPUの小さいマシンなどのクライアントマシンで有用です。メモリフットプリントが小さいアプリケーションに適しています。
- Parallel GC (-XX:+UseParallelGC): パラレルGCは、システムのCPUコア数がNであるとき、若い世代のガベージコレクション用にN個のスレッドを生成する点を除いて、シリアルGCと同じです。スレッドの数を
-XX:ParallelGCThreads=n
JVMオプションを使用して制御できます。パラレルガベージコレクタはスループットコレクタとも呼ばれ、GCパフォーマンスを高速化するために複数のCPUを使用します。パラレルGCは、古い世代のガベージコレクションに1つのスレッドを使用します。 - Parallel Old GC (-XX:+UseParallelOldGC): これはパラレルGCと同じですが、若い世代と古い世代の両方のガベージコレクションに複数のスレッドを使用します。
- Concurrent Mark Sweep (CMS) Collector (-XX:+UseConcMarkSweepGC): CMSコレクタは、同時低停止コレクタとも呼ばれます。これは古い世代のガベージコレクションを行います。CMSコレクタは、ほとんどのガベージコレクション作業をアプリケーションスレッドと同時に行うことで、ガベージコレクションによる停止時間を最小限に抑えようとします。若い世代のCMSコレクタは、パラレルコレクタと同じアルゴリズムを使用します。このガベージコレクタは、長い一時停止時間を許容できない応答性のあるアプリケーションに適しています。CMSコレクタのスレッド数を制限するには、
-XX:ParallelCMSThreads=n
JVMオプションを使用できます。 - G1 Garbage Collector (-XX:+UseG1GC):Garbage FirstまたはG1ガベージコレクターは、Java 7から利用可能であり、その長期目標はCMSコレクターを置き換えることです。G1コレクターは、並行、同時、および段階的にコンパクト化された低遅延のガベージコレクターです。Garbage Firstコレクターは他のコレクターとは異なる動作をしますが、Young世代とOld世代の空間の概念はありません。それはヒープスペースを複数の同じサイズのヒープ領域に分割します。ガベージコレクションが呼び出されると、まずライブデータが少ない領域を収集します。そのため、「Garbage First」と呼ばれます。詳細については、Garbage-First Collector Oracle Documentationを参照してください。
Javaのメモリ管理 – Javaガベージコレクションのモニタリング
JavaコマンドラインおよびUIツールを使用して、アプリケーションのガベージコレクション活動を監視できます。私の例では、Java SEダウンロードで提供されているデモアプリケーションの1つを使用しています。同じアプリケーションを使用したい場合は、Java SEダウンロードページに移動してJDK 7およびJavaFXデモおよびサンプルをダウンロードしてください。私が使用しているサンプルアプリケーションはJava2Demo.jarで、jdk1.7.0_55/demo/jfc/Java2D
ディレクトリにあります。ただし、これはオプションの手順であり、任意のJavaアプリケーションに対してGCモニタリングコマンドを実行できます。私がデモアプリケーションを開始するために使用したコマンドは次のとおりです:
pankaj@Pankaj:~/Downloads/jdk1.7.0_55/demo/jfc/Java2D$ java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar Java2Demo.jar
jstat
私たちはjstat
コマンドラインツールを使用してJVMメモリとガベージコレクション活動を監視できます。これは標準のJDKに付属しているため、それを取得するための他の手順は必要ありません。jstat
を実行するには、アプリケーションのプロセスIDを知る必要があります。これはps -eaf | grep java
コマンドを使用して簡単に取得できます。
pankaj@Pankaj:~$ ps -eaf | grep Java2Demo.jar
501 9582 11579 0 9:48PM ttys000 0:21.66 /usr/bin/java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseG1GC -jar Java2Demo.jar
501 14073 14045 0 9:48PM ttys002 0:00.00 grep Java2Demo.jar
したがって、私のJavaアプリケーションのプロセスIDは9582です。今度は、以下に示すようにjstatコマンドを実行できます。
pankaj@Pankaj:~$ jstat -gc 9582 1000
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
1024.0 1024.0 0.0 0.0 8192.0 7933.3 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8026.5 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8030.0 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8122.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 0.0 0.0 8192.0 8171.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654
1024.0 1024.0 48.7 0.0 8192.0 106.7 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656
1024.0 1024.0 48.7 0.0 8192.0 145.8 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656
jstatの最後の引数は、各出力間の時間間隔を示しています。したがって、メモリとガベージコレクションデータは1秒ごとに出力されます。まず、各列を1つずつ見てみましょう。
- S0CおよびS1C: この列は、Survivor0およびSurvivor1領域の現在のサイズ(KB)を示しています。
- S0UおよびS1U: この列は、Survivor0およびSurvivor1領域の現在の使用量(KB)を示しています。どちらかのSurvivor領域が常に空であることに注意してください。
- EC と EU:これらの列は、Eden スペースの現在のサイズと使用状況を KB 単位で示しています。EU サイズが増加し、EC を超えるとすぐに Minor GC が呼び出され、EU サイズが減少します。
- OC と OU:これらの列は、古い世代の現在のサイズと現在の使用状況を KB 単位で示しています。
- PC と PU:これらの列は、Perm Gen の現在のサイズと現在の使用状況を KB 単位で示しています。
- YGC と YGCT:YGC 列は、若い世代で発生した GC イベントの数を表示します。YGCT 列は、若い世代の GC 操作の累積時間を表示します。EU の値がマイナー GC によって減少した同じ行で、両方が増加することに注意してください。
- FGC と FGCT:FGC 列は、発生したフル GC イベントの数を表示します。FGCT 列は、フル GC 操作の累積時間を表示します。フル GC の時間が若い世代の GC 時間と比較して非常に高いことに注意してください。
- GCT:この列は、GC 操作の累積時間の合計を表示します。これは、YGCT 列と FGCT 列の値の合計です。
jstatの利点は、GUI がないリモートサーバーでも実行できることです。S0C、S1C、および EC の合計が -Xmn10m
JVM オプションで指定されたように 10m であることに注意してください。
Java VisualVM with Visual GC
GUI でメモリと GC 操作を見たい場合は、jvisualvm
ツールを使用できます。Java VisualVM は JDK の一部でもありますので、別途ダウンロードする必要はありません。ターミナルで jvisualvm
コマンドを実行して、Java VisualVM アプリケーションを起動します。起動したら、下の画像に示すように、Tools -< Plugins オプションから Visual GC プラグインをインストールする必要があります。 Visual GC をインストールしたら、左側の列からアプリケーションを開き、Visual GC セクションに移動します。下の画像に示すように、JVM メモリとガベージコレクションの詳細を取得できます。
Java Garbage Collection Tuning
Javaガベージコレクションのチューニングは、アプリケーションのスループットを向上させるための最後のオプションであり、アプリケーションのタイムアウトを引き起こすような長いGCのタイミングによるパフォーマンスの低下が見られる場合にのみ使用する必要があります。ログでjava.lang.OutOfMemoryError: PermGen space
エラーが表示される場合は、-XX:PermGenおよび-XX:MaxPermGen JVMオプションを使用してPerm Genメモリスペースを監視および増加させてみてください。また、-XX:+CMSClassUnloadingEnabled
を使用して、CMSガベージコレクターでのパフォーマンスを確認してみてください。フルGC操作が多い場合は、Old世代メモリスペースを増やすことを検討してください。全体的に、ガベージコレクションのチューニングには多くの労力と時間がかかり、それには厳密なルールはありません。異なるオプションを試して比較し、最適なものを見つける必要があります。これでJavaメモリモデル、Javaのメモリ管理、およびガベージコレクションに関する説明は終わりです。JVMメモリとガベージコレクションプロセスを理解するのに役立つことを願っています。
Source:
https://www.digitalocean.com/community/tutorials/java-jvm-memory-model-memory-management-in-java