みなさん、こんにちは。私はキャベツに支配されている豚です。
勉強が大好きで、眠れず、食べるのを忘れている人は、女の子のシックで落ち着いた、無関心なコーディングのハンサムな男の子に夢中です。
私のテキストが気に入ったら、「このキャベツを手放して、私を来させてください」という公開アカウントに従ってください。
記事のディレクトリ
09-メソッドエリア
1.スタック、ヒープ、およびメソッド領域間の相互作用
スレッド共有の観点から
簡単な例で、それらの関係をはっきりと見ることができます。
人人=新しい人()
newから出てくるものがJavaヒープに配置されている限り、クラステンプレートはメソッド領域に配置されます。
第二に、メソッド領域の理解
メソッド領域はどこですか
「Java仮想マシン仕様」には、「すべてのメソッド領域は論理的にヒープの一部ですが、一部の単純な実装ではガベージコレクションまたは圧縮の実行を選択できない場合があります」と明記されています。ただし、HotSpotJVMの場合、メソッド領域にはNonというエイリアスもあります。 -ヒープ(非ヒープ)、目的はペアから分離することです。
したがって、メソッド領域はJavaヒープから独立したメモリ空間と見なされます。
メソッド領域の基本的な理解
- メソッド領域は、Javaヒープと同様に、すべてのスレッドで共有されるメモリ領域です。
- メソッド領域はJVMの起動時に作成され、その実際の物理メモリ空間はJavaヒープ領域のように不連続になる可能性があります。
- ヒープスペースと同じメソッド領域のサイズは、固定または拡張可能です。
- メソッド領域のサイズによって、システムが保存できるクラスの数が決まります。システムが定義するクラスが多すぎてメソッド領域がオーバーフローした場合、仮想マシンはメモリオーバーフローエラーも除外します:java.lang.OutOfMemoryError:PermGenスペースまたはjava.lang .OutOfMemoryError:メタスペース
- 多数のサードパーティjarパッケージをロードします。Tomcatにデプロイされているプロジェクトが多すぎ(30〜50)、多数の動的リフレクションクラスが生成されます。
- JVMを閉じると、この領域のメモリが解放されます。
HotSpotのメソッド領域の進化
- jdk7以前では、メソッド領域を永続世代と呼ぶのが通例です。jdk8以降、永続世代はメタスペースに置き換えられます。
- 本質的に、メソッド領域と永続的な生成は同等ではありません。ホットスポットのみ。「Java仮想マシン仕様」では、メソッド領域の実装方法について統一された要件はありません。例:BEA JRockit / IBMJ9には永続生成の概念はありません。
- 今それを見ると、当時の永久世代を使用することは良い考えではありませんでした。JavaプログラムをOOMしやすくします(-XXを超える:MaxPermSizeの上限)
- JDK 8では、永続生成の概念は最終的に完全に廃止され、JRockitやJ9などのローカルメモリに実装されているMetaspaceに置き換えられました。
- メタスペースの性質は、JVM仕様のメソッド領域の実現である永続生成の性質と似ています。ただし、メタスペースと永続的な生成の最大の違いは、メタスペースが仮想マシンによって設定されたメモリになく、ローカルメモリを使用することです。
- 永続的な世代とメタスペースの両方で、名前が変更されただけでなく、内部構造も調整されました。
- 「Java仮想マシン仕様」によると、メソッド領域が新しいメモリ割り当て要件を満たすことができない場合、OOM例外がスローされます。
3.メソッドエリアサイズとOOMの設定
メソッド領域メモリのサイズを設定します
-
メソッド領域のサイズを固定する必要はありません。アプリケーションのニーズに応じて、jvmを動的に調整できます。
-
jdk7以前:
- -XX:PermSizeを使用して、永続世代の初期割り当てスペースを設定します。デフォルト値は20.75Mです
- -XX:MaxPermSizeは、永続世代の割り当て可能な最大スペースを設定します。デフォルトは、32ビットマシンの場合は64M、64ビットマシンの場合は82Mです。
- JVMによってロードされるクラス情報の容量がこの値を超えると、例外OutOfMemoryError:PermGenスペースが報告されます。
-
jdk 8以降:
- メタデータ領域のサイズは、上記の2つの元のパラメーターの代わりに、パラメーター-XX:MetaspaceSizeおよび-XX:MaxMetaspaceSizeを使用して指定できます。
- デフォルト値はプラットフォームによって異なります。ウィンドウでは、-XX:MetaspaceSizeは21M、-XX:MaxMetaspaceSizeは-1です。つまり、制限はありません。
- 永続的な生成とは異なり、サイズを指定しない場合、デフォルトでは、仮想マシンは使用可能なすべてのシステムメモリを使い果たします。メタデータ領域メソッドがオーバーフローすると、仮想マシンは例外OutOfMemoryError:Metaspaceもスローします。
- -XX:MetaspaceSize:初期メタスペースサイズを設定します。64ビットのサーバー側JVMの場合、デフォルトの-XX:MetaspaceSize値は21MBです。これが最初の最高水準点です。この最高水準点に触れると、フルGCがトリガーされ、不要なクラスがアンロードされ(つまり、これらのクラスに対応するクラスローダーは存続しなくなります)、この最高水準点がリセットされます。新しい最高水準点の値は、GC後に解放されるスペースの量によって異なります。解放されたスペースが不十分な場合は、MaxMetaspaceSizeを超えない範囲で値を適切に増やします。空き容量が多すぎる場合は、値を適切に下げてください。
- 初期化された最高水準点の設定が低すぎると、上記の最高水準点の調整が何度も行われます。ガベージコレクタのログから、フルGCが複数回呼び出されていることがわかります。頻繁なGCを回避するために、-XX:MetaspaceSizeを比較的高い値に設定することをお勧めします。
これらのOOMを解けば
1. OOM例外またはヒープスペース例外を解決するための一般的な方法は、最初にメモリイメージ分析ツール(Eclipse Memory Analyzerなど)を使用してダンプからのヒープダンプスナップショットを分析することです。焦点は、メモリが必要です。つまり、最初にメモリリーク(メモリリーク)またはメモリオーバーフロー(メモリオーバーフロー)があるかどうかを区別する必要があります。
2.メモリリークの場合は、ツールを使用して、リークされたオブジェクトからGCルートへの参照チェーンを1つの手順で確認できます。次に、リークされたオブジェクトがGCルートにどのように関連しているかを調べ、ガベージコレクターがそれらを自動的に収集できないようにすることができます。リークされたオブジェクトのタイプ情報とGCRootsリファレンスチェーンの情報がわかれば、リークされたコードの場所をより正確に見つけることができます。
3.メモリリークがない場合、つまりメモリ内のすべてのオブジェクトがまだ生きている必要がある場合は、仮想マシンのヒープパラメータ(-Xmxおよび-Xms)を確認し、マシンの物理メモリと比較する必要があります。調整できるかどうかを確認します。大きい場合は、コードから、一部のオブジェクトのライフサイクルが長く、状態が長すぎる状況がないかどうかを確認し、プログラムの実行中のメモリ消費量を削減してみてください。
注:ツールを使用してスナップショットを取得し、それがメモリオーバーフローかメモリリークかを確認します。メモリリークの場合は、ツールを使用してGCルートリファレンスチェーンをさらに確認し、リークされたオブジェクトのタイプ情報をマスターして、リークされたコード。メモリがオーバーフローした場合は、パラメータを調整して、ライフサイクルの長いオブジェクトがあるかどうかを確認します。
第四に、メソッド領域の内部構造
メソッド領域に保存されているもの
「Java仮想マシンの詳細な理解」という本のメソッド領域のストレージコンテンツの説明は次のとおりです。
これは、仮想マシンによってロードされたジャストインタイムコンパイラによってコンパイルされた型情報、定数、静的変数、およびコードキャッシュを格納するために使用されます。
[外部リンク画像の転送に失敗しました。ソースサイトにアンチホットリンクメカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-ajiVpbkn-1610109790715)(E:\ 2020 Winter Vacation Study Plan \ Study Notes \ JVM \ images \ Chapter 09_ Method area storage information.jpg)]
メソッド領域の内部構造
タイプ情報
ロードされたタイプ(クラスクラス、インターフェイスインターフェイス、列挙型列挙型、アノテーションアノテーション)ごとに、JVMは次のタイプ情報をメソッド領域に格納する必要があります。
- このタイプの完全で有効な名前(フルネーム=パッケージ名。クラス名)
- このタイプの直接の親の完全で有効な名前(インターフェースまたはjava.lang.Objectの場合、親はありません)
- このタイプの修飾子(public、abstract、finalの特定のサブセット)
- このタイプの直接インターフェースの順序付きリスト
フィールド情報
- JVMは、タイプのすべてのフィールド関連情報とフィールドの宣言順序をメソッド領域に格納する必要があります。
- ドメインの関連情報には、ドメイン名、ドメインタイプ、ドメイン修飾子(パブリック、プライベート、保護、静的、最終、揮発性、一時的のサブセット)が含まれます。
メソッド情報
JVMは、ドメイン情報などの宣言の順序を含む、すべてのメソッドの次の情報を保存する必要があります。
- メソッド名
- メソッドの戻り値の型(またはvoid)
- メソッドパラメータの数とタイプ(順番に)
- メソッド修飾子(public、private、protected、static、final、synchronized、native、abstractのサブセット)
- メソッドのバイトコード、オペランドスタック、ローカル変数のテーブルとサイズ(抽象メソッドとネイティブメソッドを除く)
- 例外テーブル(抽象メソッドとネイティブメソッドを除く)
- 各例外処理の開始位置と終了位置、プログラムカウンタのコード処理のオフセットアドレス、およびキャッチされた例外クラスの定数プールインデックス
非最終クラス変数
- 静的変数とクラスは相互に関連付けられており、クラスのロード時にロードされ、クラスデータの論理部分になります。
- クラス変数はクラスのすべてのインスタンスで共有され、クラスインスタンスがない場合でもアクセスできます。
補足:グローバル定数:静的ファイナル
finalとして宣言されたクラス変数の処理方法は異なります。各グローバル定数はコンパイル時に割り当てられます。最終的な変更がない場合、prepareには接続時にゼロの値が割り当てられ、初期化中に割り当てられます。
ランタイム定数プールと定数プール
- 実行時定数プールを含むメソッド領域
- 定数プールを含むバイトコードファイル
- ロードされたクラスの情報はメソッド領域にあるため、メソッド領域を理解するには、ClassFileを理解する必要があります。
- メソッド領域の実行時定数プールを理解するには、ClassFileの定数プールを理解する必要があります。
クラスバージョン情報、フィールド、メソッド、およびインターフェイスの説明情報に加えて、有効なバイトコードファイルには、さまざまなリテラルとタイプフィールドのペアを含む定数ビリヤード台(定数ビリヤード台)である情報も含まれています。そして、メソッドへのシンボリック参照。
なぜ一定のプールが必要なのですか
Javaソースファイルのクラスとインターフェイスは、バイトコードファイルを生成するためにコンパイルされます。Javaのバイトコードにはデータのサポートが必要です。通常、この種のデータは大きすぎてバイトコードに直接格納できません。別の方法では、定数プールに格納できます。このバイトコードには定数プールへのポインタが含まれています。参照。ランタイム定数プールは、以前に導入されたダイナミックリンク中に使用されます。
たとえば、次のコードはわずか194バイトですが、String、System、PrintStream、Objectなどの構造を使用しています。ここでのコードの量は実際には非常に少ないです。より多くのコードがある場合、より多くの構造が参照されるため、ここでは定数プールが必要です。
定数プールには何がありますか
定数プールに格納されるいくつかのデータ型には、数量値、文字列値、クラス参照、フィールド参照、メソッド参照が含まれます。
概要:
定数プールはテーブルと見なすことができます。仮想マシンの命令は、この定数テーブルに従って実行されるクラス名、メソッド名、パラメータタイプ、リテラル値などのタイプを検出します。
ランタイム定数プール
- ランタイム定数プールはメソッド領域の一部です。
- 定数ビリヤード台は、コンパイラによって生成されたさまざまなリテラルおよびシンボル参照を格納するために使用されるクラスファイルの一部です。コンテンツのこの部分は、クラスがロードされた後、メソッド領域の実行時定数プールに格納されます。
- ランタイム定数プール。クラスとインターフェイスを仮想マシンにロードした後、対応するランタイム定数プールが作成されます。
- JVMは、ロードされたタイプ(クラスまたはインターフェース)ごとに一定のプールを維持します。プール内のデータ項目は配列項目のようなものであり、インデックスによってアクセスされます。
- ランタイム定数プールには、コンパイル時にすでにクリアされている数値リテラルを含むさまざまな定数が含まれており、メソッドまたはフィールドの参照は、ランタイム解析後にのみ取得できます。現時点では、定数プール内のシンボリックアドレスではなく、ここで実際のアドレスに置き換えられています。
- クラスファイル定数プールと比較したランタイム定数プールは、もう1つの重要な機能です。動的です。
- String.intern()
- クラスファイル定数プールと比較したランタイム定数プールは、もう1つの重要な機能です。動的です。
- ランタイム定数プールは、従来のプログラミング言語のシンボルテーブルに似ていますが、そこに含まれるデータはシンボルテーブルによって強化されています。
- クラスまたはインターフェイスのランタイム定数プールを作成するときに、ランタイム定数プールの構築に必要なメモリスペースがメソッド領域が提供できる最大値を超えると、JVMはOutOfMemoryError例外をスローします。
5.メソッド領域の使用例
6、メソッド領域の進化の詳細
- まず第一に、それは明らかです:HotSpotだけが永続的な世代を持っています。BEA JRockit、IBM J9などの場合、永続的な生成の概念はありません。原則として、実装方法の領域が仮想マシンの実装の詳細に属している場合、それは「Java仮想マシン仕様」に準拠せず、統一性を必要としません。
- HotSpotのメソッド領域の変更:
jdk1.6以前 | 永続的な世代があり、静的変数は永続的な世代に格納されます |
---|---|
jdk1.7 | 永続的な世代がありますが、徐々に「永続的な世代に移行」しています。文字列定数プールと静的変数は削除され、ヒープに格納されます。 |
jdk1.8以降 | 永続的な生成、型情報、フィールド、メソッド、および定数はローカルメモリのメタ空間に格納されませんが、文字列定数プールと静的変数はまだヒープ内にあります |
永続的な世代をメタスペースに置き換える必要がある理由
「Java仮想マシン仕様」には、JRockitには永続的な世代がないため、交換が必要な理由が記載されているため、永続的な世代が置き換えられます。これは、なぜ整列する必要があるのかを尋ねるようなものです。他の人が並んでいて、私も並んでいるのに、なぜ他の人が並んでいるのですか?
- Java 8の登場により、HotSpotVMに永続的な世代はなくなります。ただし、これは、クラスのメタデータ情報も消えたことを意味するものではありません。これらのデータは、ヒープに接続されていないローカルメモリ領域に移動されます。この領域はメタスペース(メタスペース)と呼ばれます。
- クラスのメタデータはローカルメモリに割り当てられるため、メタスペースの割り当て可能な最大スペースは、システムで使用可能なメモリスペースです。
- この変更は、次の理由で必要です。
- **恒久的な世代のためのスペースのサイズを決定することは困難です。**一部のシナリオでは、動的にロードされるクラスが多すぎると、Perm領域でOOMが発生する可能性があります。たとえば、実際のWebプロジェクトでは、ファンクションポイントが多数あるため、実行中のプロセス中に多くのクラスを動的にロードする必要があり、致命的なエラーが頻繁に発生します。メタスペースと永続的な生成の最大の違いは、メタスペースが仮想マシンになく、ローカルメモリを使用することです。したがって、デフォルトでは、メタスペースのサイズはローカルメモリによってのみ制限されます。
- 永続的な世代の調整は非常に困難です。
文字列テーブルを調整する必要がある理由
StringTableをjdk7のヒープスペースに配置します。永久世代の収集効率は非常に低いため、完全なGC中にのみトリガーされます。また、完全なgcは、古い世代に十分なスペースがなく、永続的な世代が不十分な場合にのみトリガーされます。その結果、StringTableのリサイクル効率は高くありません。私たちの開発では、多数の文字列が作成され、リサイクル効率が低く、永続的な生成メモリが不足します。メモリが回復したとしても、それを山に置いてください。
「Java仮想マシン仕様」で定義された概念モデルの観点から、クラス関連の情報はすべてメソッド領域に格納する必要がありますが、「Java仮想マシン仕様」ではメソッド領域の実装方法を指定していません。さまざまな仮想マシンが柔軟に自分自身を制御できるようにするもの。JDK 7以降のバージョンのHotSpot仮想マシンは、静的変数とJava言語側のタイプのマップされたClassオブジェクトを一緒に格納し、それらをJavaヒープに格納することを選択します。これは実験から明確に確認されています。
7.メソッドエリアでのガベージコレクション
メソッド領域(HotSpot仮想マシンでのメタスペースや永続的な生成など)にはガベージコレクションの動作がないと考える人もいますが、そうではありません。「Java仮想マシン仕様」では、メソッド領域の制約が非常に緩く、メソッド領域にガベージコレクションを実装する必要がない場合があるとのことです。実際、メソッド領域タイプのオフロードを実装していない、または完全に実装できなかったコレクターが存在します(たとえば、JDK 11期間のZGCコレクターは累積オフロードをサポートしていません)。
一般的に言って、この地域での回復効果、特に荷降ろしの種類を満足させることは困難であり、条件は非常に厳しいです。しかし、地域のこの部分のリサイクルが実際に必要な場合もあります。以前のSun社のバグリストで発生したいくつかの重大なバグは、この領域を完全に再利用しなかったHotSpot仮想マシンの低バージョンが原因であり、メモリリークが発生していました。
メソッド領域のガベージコレクションは、主に2つの部分をリサイクルします。定数プール内の破棄された定数と、使用されなくなった型です。
- 最初に、メソッド領域の定数プールに格納されている2つの主要なタイプの定数(リテラルとシンボル参照)について説明します。リテラルは、テキスト文字列、finalとして宣言された定数値など、Java言語レベルの定数の概念に比較的近いものです。シンボリックリファレンスは、次の3種類の定数を含む、コンパイル原理メソッドの概念に属しています。
- クラスとインターフェースの完全修飾名
- フィールド名と記述子
- メソッド名と記述子
- 定数プールに対するHotSpot仮想マシンのリサイクル戦略は非常に明確です。定数プール内の定数がどこにも参照されていない限り、それらはリサイクルできます。
- 廃止された定数のリサイクルは、Javaヒープ内のオブジェクトの再利用と非常によく似ています。
しかし、引用されていないかどうかを判断するのは難しいです
- 定数が「非推奨」であるかどうかを判断するのは比較的簡単であり、型が「使用されなくなったクラス」に属しているかどうかを判断する条件はより厳しいものです。次の3つの条件を同時に満たす必要があります。
- このクラスのすべてのインスタンスはリサイクルされています。つまり、このクラスのインスタンスはなく、Javaヒープには派生サブクラスはありません。
- クラスをロードしたクラスローダーはリサイクルされています。この条件は、OSGiやJSPのリロードなど、慎重に設計された代替クラスローダーシナリオでない限り、通常は達成が困難です。
- このクラスに対応するjava.lang.Classオブジェクトはどこにも参照されておらず、このクラスのメソッドにはリフレクションを介してどこからもアクセスできません。
- Java仮想マシンは、上記の3つの条件を満たす不要なクラスのリサイクルを許可されています。ここで言うことは、オブジェクトと同じではなく、「許可」のみです。参照がない場合は、リサイクルされます。タイプをリサイクルするかどうかに関して、HotSpot仮想マシンは制御する-Xnoclassgcパラメーターを提供します。また、-verbose:classおよび-XX:+ TraceClass-Loading、-XX:TraceClassUnloadingを使用して、クラスのロードおよびアンロード情報を表示することもできます。
- リフレクション、動的プロキシ、CGLib、およびその他のバイトコードフレームワークを使用してJSPおよびOSGiの頻繁にカスタマイズされるクラスローダーを動的に生成する多数のシナリオでは、通常、Java仮想マシンが型をアンロードしてエラーは発生しません。メソッド領域は過度のメモリプレッシャーを引き起こします。