序文
最初に明確にする必要があるのは、以下で説明するHotSpot仮想マシンと、JRockitやJ9などの他のタイプの仮想マシンには、永続的な生成の概念がまったくないということです。したがって、下記の「仮想マシン」はすべてHotSpotバージョンです。
この変更の理由を理解するには、まずメソッド領域の概念、永続的な世代、メタスペースの関係を理解する必要があります。
メソッド領域と永続的な生成、メタ空間の関係
メソッド領域は仕様です。仕様に基づいて、異なる仮想マシンメーカーが異なる実装を行うことができます。永続的な生成とメタ空間は、異なるjdkバージョンの実装に基づいています。
端的に言えば、メソッド領域はインターフェイスのようなもので、永続的な生成とメタ空間は2つの異なる実装クラスです。永続的な世代がこのインターフェースの初期実装クラスであるというだけのことですが、このインターフェースは、実装クラスが完全に放棄され、新しい実装クラスのメタ空間に置き換えられるまで変更されました。
メソッドエリア
「Java仮想マシン-JVMの高度な機能とベストプラクティスの詳細な理解」のメソッド領域を紹介する段落をご覧ください
ヒープと同様に、メソッド領域はさまざまなスレッドで共有されるメモリ領域であり、クラス情報、定数、静的変数、仮想マシンによって読み込まれたジャストインタイムコンパイル済みコードなどのデータを格納するために使用されます。
Java 7および以前のバージョンの永続的な世代の構造
Java 7以前のバージョンでは、永続的な世代があります。Java 7バージョンでは、永続的な世代が静かに変更されました。Java 8が完成するまでに、永続的な世代は完全に放棄され、メタスペースに置き換えられました。
永続的な世代とヒープは、次のように構築されます。
(Edenの場合、ヒープ内の領域との間の領域については、私の別の記事[JVM]を参照して、Javaのヒープ領域について話すことができます)
上の図からわかるように、ヒープ内の永続世代と旧世代は連続していますが、ここでの連続とは連続物理アドレスを指し、永続世代自体はヒープ内にありません。したがって、古い世代と永続的な世代のいずれかがいっぱいになると、両方がフルGCをトリガーします。
次のコマンドを使用して、指定した永続的な世代のサイズを表示できます。
- -XX:PremSize:永続的な世代の初期サイズを設定します
- -XX:MaxPermSize:永続的な世代の最大値を設定します
メソッド領域は主にクラスの関連情報を格納するため、クラスを動的に生成する場合のメモリオーバーフローの永続的な生成は比較的容易です。最も一般的なシナリオは、多くのjspページがある場合、メモリオーバーフローの永続的な生成が発生する傾向があり、「java.lang.OutOfMemoryError:PermGen space」例外が報告されることです。
Java 7では、永続的な世代の変更
Java 7では、永続的な世代がまだあり、永続的な世代もヒープ内の古い世代と連続していますが、永続的な世代に格納されているデータの一部は、次のようなJavaヒープまたはネイティブメモリに転送され始めています。
- シンボルはネイティブメモリに転送されます
- 文字列定数プール(インターンされた文字列)がJavaヒープに移動
- クラス静的はJavaヒープに転送されます
次に、Java6、7、および8の環境でそれぞれString.intern()メソッドを呼び出します(このメソッドについては、最初に他の記事[JAVA] String source code talkに移動できます。このメソッドの概要があります)そして実験)、その後、次の領域のメモリオーバーフロー例外がそれぞれ報告されます
- Java 6では、メモリオーバーフロー領域は永続的な世代です。Java 6以前では、文字列定数プールは永続的な世代にあるため
- Java 7では、メモリオーバーフロー領域はヒープ内にあります。Java 7では、文字列定数プールがヒープに移動されたためです。
- Java 8では、メモリオーバーフロー領域はまだヒープ内にありますが、現時点では永続的な世代はありません。
Java 8以降、永続的な世代は姿を消し、メタスペースに置き換えられました。
メタスペース
メタスペースはヒープと連続しなくなりましたが、マシンのメモリであるローカルメモリに直接存在します。理論的には、マシンのメモリと同様に、メタスペースの野望も同じです。ただし、メタスペースのサイズは次のパラメータで設定できます。
- -XX:MetaspaceSize、初期スペースサイズ、この値に到達すると、タイプのアンロードのガベージコレクションがトリガーされ、GCが値を調整します。大量のスペースが解放されると、値は適切に減少し、少量のスペースが解放されると、MaxMetaspaceSizeを超えない場合は、値を適切に増やします。
- -XX:MaxMetaspaceSize、最大スペース。デフォルトでは制限はありません。
上記の2つのサイズ固有のオプションに加えて、2つのGC関連のプロパティがあります。
- -XX:MinMetaspaceFreeRatio、GC後、メタスペースの残りの最小容量のパーセンテージ。割り当てられたスペースによるガベージコレクションを削減します。
- -XX:MaxMetaspaceFreeRatio、GC後、最大のメタスペースの残りスペース容量のパーセンテージ。スペースの解放によって発生するガベージコレクションを削減します。
-XX:MaxMetaspaceSizeを使用して、指定されたメタスペースのサイズが比較的小さい値であることを示し、ループに動的に読み込まれるクラスが多すぎる場合、「java.lang.OutOfMemoryError:Metaspace」例外が報告されます。
Java 8では、PremSizeまたはMaxPermSizeを使用して永続的な世代のサイズを設定すると、無視され、コンパイラーによって警告されます。
Java8で永続的な世代をメタスペースに置き換える理由
以前のバージョンでは、文字列定数プールは永続的な世代に存在していましたが、文字列の数が多い場合、OOMは非常に例外が発生しやすくなります。さらに、 J VMによってロードされたクラスの総数とメソッドのサイズを判別するのは難しいため、永続的な世代のサイズを判別することは困難です。永続的な世代が小さすぎると、永続的な世代でメモリオーバーフローが発生しやすくなり、永続的な世代が大きすぎると、仮想マシンでメモリ不足が発生しやすくなります。
もちろん、さらに多くの詳細な理由があります。このブログポストメタスペース1を参照できます:メタスペース全体の概要(永続的な生成が置き換えられた理由、メタスペースの特性、メタスペースのメモリビュー分析方法)