JVM のメモリ領域はどのように分割されていますか? JVM のメモリ領域では、一部の領域はスレッド専用であり、一部は JVM プロセス全体に属します。一部の領域では OOM 例外がスローされますが、他の領域ではスローされません。 JVM のメモリ領域の分割と特性を理解することは、オンライン メモリの問題を特定するための基礎となります。では、JVM メモリ領域はどのように分割されるのでしょうか?
1 つ目はプログラム カウンター (プログラム カウンター レジスタ) です。 JVM 仕様では、各スレッドには独自のプログラム カウンターがあります。これは、現在のスレッドによって実行されている Java メソッドの JVM 命令アドレス、つまりバイトコードの行番号を格納する比較的小さなメモリ領域です。ネイティブ メソッドが実行されている場合、このカウンターは空になります。このメモリ領域は、Java 仮想マシン仕様で OOM 状況が指定されていない唯一の領域です。 2 番目に、Java 仮想マシン スタック (Java Virtual Machine Stack) もスレッド プライベート領域に属します。各スレッドは、作成時に仮想マシン スタックを作成します。ライフサイクルはスレッドと一致します。スレッドが終了すると、スレッドの仮想マシン スタックもリサイクルされます。仮想マシン スタックはスタック フレームを 1 つずつ維持し、各メソッド呼び出しによってスタックがプッシュされます。 JVM には、スタック フレームに対する pop と push の 2 つの操作しかありません。メソッド呼び出しが終了すると、ポップ操作が実行されます。 この領域には、ローカル変数テーブル、コンパイル時に認識されるさまざまな基本データ タイプ、オブジェクト参照、メソッド終了、およびその他の情報が格納されます。 3 番目に、ネイティブ メソッド スタックは仮想マシン スタックに似ています。ネイティブ メソッド スタックは、ネイティブ メソッドを呼び出すときに使用されるスタックです。各スレッドにはネイティブ メソッド スタックがあります。 4番目はヒープです。作成された Java オブジェクト インスタンスのほぼすべては、ヒープ領域に直接割り当てられます。ヒープはすべてのスレッドで共有され、ヒープ上の領域はガベージ コレクターによってさらに新しい世代や古い世代などに分割されます。 Java 仮想マシンの起動時に、「Xmx」などのパラメータを使用してヒープ領域のサイズを指定できます。 5番目は、メソッド領域(Method Area)です。メソッド領域はヒープと同様にすべてのスレッドで共有され、クラス情報、定数、静的変数、ジャストインタイムコンパイラによってコンパイルされたコード、その他のデータなど、仮想マシンによってロードされたメタデータを格納します。ここで注目すべきは、ランタイム定数プールもメソッド領域にあるということです。 Java 仮想マシン仕様によれば、メソッド領域がメモリ割り当て要件を満たせない場合、OutOfMemoryError 例外がスローされます。 HotSpot JVM の初期の実装により、CG 世代コレクションがメソッド領域に拡張されたため、メソッド領域は永続世代と呼ばれることがよくあります。 Oracle JDK8 では、永続世代が削除され、メタデータ領域 (Metaspace) が追加されました。 6 番目は、メソッド領域の一部であり、メソッド領域のメモリによって制限されるランタイム定数プールです。定数プールがメモリに適用できなくなると、OutOfMemoryError 例外がスローされます。 クラス ファイルには、クラスのバージョン、メソッド、フィールド、インターフェイス、その他の説明情報に加えて、定数プールもあります。各クラス ファイルの最初の 4 バイトはマジック ナンバーと呼ばれ、仮想マシンが受け入れ可能なファイルかどうかを判断するために使用されます。次の 4 バイトには、クラス ファイルのバージョン番号が格納されます。バージョン番号のすぐ後には定数プールのエントリがあります。定数プールには主に 2 種類の定数が格納されます。
クラス ファイル内の定数プールは、静的定数プールとも呼ばれます。 JVM 仮想マシンはクラスのロード操作を完了すると、静的定数プールをメモリにロードし、ランタイム定数プールに格納します。 第七に、直接記憶(ダイレクトメモリ)。直接メモリは、Java 仕様で指定されている Java 仮想マシンのランタイム データ領域の一部ではありません。 Java の NIO はネイティブ メソッドを使用して Java ヒープの外部に直接メモリを割り当て、このオフヒープ メモリへの参照として DirectByteBuffer オブジェクトを使用できます。 次の図は、実行中の Java プロセスのメモリ使用量を示しています。 OOM はどの領域で発生する可能性がありますか? javadoc の説明によると、OOM は JVM のメモリが不足し、ガベージ コレクターがそれ以上のメモリを提供できないことを意味します。説明からわかるように、JVM が OutOfMemoryError をスローする前に、ガベージ コレクターは通常、まずメモリの再利用を試みます。 上記で分析した Java データ領域のうち、プログラム カウンターを除くどの領域に OOM が発生しますか? まず、ヒープメモリ。ヒープ メモリの不足は、OOM を送信する最も一般的な理由の 1 つです。オブジェクト インスタンスの割り当てを完了するためのメモリがヒープ内に存在せず、ヒープを拡張できなくなった場合は、OutOfMemoryError 例外がスローされます。現在の主流の JVM は、-Xmx と -Xms を通じてヒープ メモリのサイズを制御できます。ヒープ上の OOM は、メモリ リークまたは不当なヒープ サイズの割り当てによって発生する可能性があります。 2 番目は、Java 仮想マシン スタックとネイティブ メソッド スタックです。これら 2 つの領域の違いは、仮想マシン スタックは仮想マシンに Java メソッドの実行を提供するのに対し、ネイティブ メソッド スタックは仮想マシンが使用するネイティブ メソッドを提供するという点です。メモリ割り当て例外でも同様です。 JVM 仕様では、Java 仮想マシン スタックに対して 2 つの例外が定義されています。1. スレッドによって要求されたスタックが割り当てられたスタック サイズより大きい場合、停止しない再帰呼び出しなどの StackOverFlowError エラーがスローされます。 2. 仮想マシン スタックを動的に拡張でき、拡張中に十分なメモリを要求できない場合は、OutOfMemoryError エラーがスローされます。 3番目は、直接的な記憶です。ダイレクトメモリは仮想マシンの実行時にはデータ領域の一部ではありませんが、メモリであるため、物理メモリによって制限されます。 JDK1.4 で導入された NIO は、ネイティブ関数ライブラリを使用してオフヒープメモリ上に直接メモリを割り当てますが、直接メモリが不足すると、OOM も発生します。 4番目は、方法領域です。 Metaspace メタデータ領域の導入により、メソッド領域の OOM エラー メッセージも「java.lang.OutOfMemoryError:Metaspace」になります。 Oracle JDK の古いバージョンでは、永続世代のサイズが制限されており、永続世代のガベージ コレクションでは JVM はアクティブではありません。 String.Intern() の呼び出しなど、永続領域にデータが継続的に書き込まれると、永続領域が占有する領域が大きくなりすぎてメモリ不足が発生し、OOM の問題も発生する可能性があります。対応するエラー メッセージは「java.lang.OutOfMemoryError:PermGen space」です。 ヒープメモリ構造はどのようなものですか? いくつかのツールを使用して、JVM のメモリの内容を理解することができます。具体的には、特定のメモリ領域を見つけるにはどのようなツールを使用すればよいでしょうか? グラフィカルツール。グラフィカル ツールの利点は、直感的であることです。 Java プロセスに接続すると、ヒープ メモリとオフヒープ メモリの使用状況を表示できます。同様のツールには、JConsole、VisualVm などがあります。 コマンドラインツール。このタイプのツールは実行時にクエリを実行でき、ヒープ メモリやメソッド領域などを表示できる jstat、jmap などがこれに含まれます。これらのツールは、オンラインの問題を特定するときにもよく使用されます。 jmap はヒープ ダンプ ファイルも生成できます。 Linux の場合は、ヒープ ダンプ ファイルをローカルで取得し、Eclipse MAT または jhap を使用して分析できます。 メモリの監視と診断については、後ほどさらに詳しく説明します。では次の質問を見てみましょう。ヒープの構造は何でしょうか? ガベージコレクターの観点から見ると、メモリは新しい世代と古い世代に分けられます。メモリ割り当てルールは、現在使用されているガベージ コレクターの組み合わせとメモリ関連のパラメータ構成によって異なります。一般的に、オブジェクトは最初に新しい世代の Eden 領域に割り当てられ、大きなオブジェクトは古い世代に直接割り当てられます。 まず、新世代のEden領域では、まずこの領域にオブジェクトが割り当てられます。同時に、JVM は、TLAB (Thread Local Allocation Buffer) と呼ばれる各スレッドのプライベート キャッシュ領域を割り当てることができます。これにより、複数のスレッドが同時にメモリを割り当てるときにロック メカニズムを使用する必要がなくなり、割り当て速度に影響を及ぼさなくなります。 TLAB はヒープ上に割り当てられ、Eden に配置されます。 TLAB の構造は次のとおりです。 基本的に、TLAB 管理は、開始、終了、および上部の 3 つのポインタに依存します。この TLAB によって管理される Eden 内の領域の開始と終了をマークします。この領域は、他のスレッドがメモリを割り当てるために使用されません。 Top は割り当てポインターであり、先頭の開始位置を指します。メモリ割り当てが進むにつれて、ゆっくりと終わりに近づき、終わりに達すると TLAB の補充がトリガーされます。したがって、記憶の中のエデンの構造は、おおよそ次のようになります。 2つ目は、新世代のサバイバーエリアです。 Eden 領域のメモリが不足すると、新世代 GC とも呼ばれるマイナー GC がトリガーされます。マイナー GC を生き残ったオブジェクトは、サバイバー領域にコピーされます。サバイバーエリアの役割は、Full GC が早期にトリガーされるのを防ぐことだと考えます。 Survivor がない場合、Eden 領域内のすべてのマイナー GC はオブジェクトを古い世代に直接送信し、古い世代はすぐにメモリ不足になり、Full GC がトリガーされます。新世代には 2 つのサバイバー エリアがあります。 2 つの Survivor 領域の役割は、パフォーマンスを向上させ、メモリの断片化を回避することだと考えます。いつでも、空のサバイバーが存在します。マイナー GC が発生すると、メモリの断片化を回避するために、Eden と別の Survivor の残存オブジェクトが空の Survivor にコピーされます。新世代のメモリ構造は、おおよそ次のようになります。 3番目は老齢です。古い世代は、ライフサイクルが長いオブジェクト (通常は、存続領域からコピーされたオブジェクト) を格納するために使用されます。ただし、オブジェクトが大きすぎて新しい世代の連続メモリに格納できない場合は、大きなオブジェクトが古い世代に直接割り当てられます。一般的に、通常のオブジェクトは TLAB 上に割り当てられ、より大きなオブジェクトは Eden 領域内の他のメモリ領域に直接割り当てられ、特大のオブジェクトは古い世代に直接割り当てられます。 4番目は永久世代です。前述したように、古い世代の概念は初期の Hotspot JVM に存在し、Java クラスのメタデータ、定数プール、Intern 文字列などを格納するために使用されていました。 JDK8 以降では、古い世代が削除され、メタデータ領域の概念が導入されました。 5番目は、仮想空間です。前述したように、Xms と Xmx を使用してヒープの最小スペースと最大スペースを指定できます。 Xms が Xmx より小さい場合、ヒープ サイズは上限まで直接拡張されませんが、メモリ需要が引き続き増加したときに一部が予約され、新しい世代に割り当てられます。仮想空間とは、この予約されたメモリ領域のことです。 まとめると、Java ヒープのメモリ構造は次のように表すことができます。 上記のヒープ メモリ領域のサイズは、いくつかのパラメータを通じて指定できます。 -Xmx 値は最大ヒープ サイズを指定します -Xms 値は初期最小ヒープ サイズを指定します -XX:NewSize = 値は新しい世代のサイズを指定します -XX:NewRatio = 値は古い世代と新しい世代のサイズ比を指定します。デフォルトでは、この比率は 2 です。つまり、古い世代は新しい世代の 2 倍の大きさになります。古い世代が大きすぎる場合、Full GC 時間は非常に長くなります。古い世代が小さすぎると、Full GC がトリガーされやすくなり、Full GC の頻度が高くなりすぎます。これがこのパラメータの影響です。 -XX:SurvivorRation=値。 Eden と Srivivor のサイズ比を設定します。値が 8 の場合、生存者はエデンの 1/8 であり、新しい世代全体の 1/10 であることを意味します。 一般的に使用されているパフォーマンス監視および問題箇所特定ツールは何ですか? システムのパフォーマンス分析では、CPU、メモリ、IO が主な懸念事項となります。多くの場合、サービスに問題が発生すると、CPU の急増、メモリ不足、OOM などの 3 つの側面で問題が顕在化します。この時点で、パフォーマンスを監視し、問題を特定するために、対応するツールを使用する必要があります。 CPU 監視の場合は、まず top コマンドを使用して表示します。以下は、top を使用して負荷を表示したスクリーンショットです。 負荷平均は、1 分、5 分、15 分間の平均システム負荷を表します。これら 3 つの数値から、システム負荷が大きいか小さいかを判断できます。 CPU が完全にアイドル状態のとき、平均負荷は 0 です。 CPU が完全に負荷されているとき、平均負荷は 1 です。したがって、負荷平均と他の 3 つの値が低いほど、システム負荷は軽くなります。では、システム負荷が大きいと判断できるのはいつでしょうか?この記事 (Linux CPU 負荷の理解 - いつ心配すべきか) では、これを非常に簡単に説明しています。コンピューターに CPU が 1 つしかない場合は、CPU を 1 車線だけの一方通行の橋と考えてください。すべての車はこの橋を通らなければなりません。それで システム負荷は 0 です。つまり、橋の上に車はありません。 システム負荷 0.5 は、橋の半分に車が通っていることを意味します。 システム負荷 1 は、橋が車でいっぱいであることを意味します。 システム負荷 1.7 は、橋が満杯 (100%) であり、車の 70% が橋を渡るのを待っていることを意味します。 top コマンドのスクリーンショットから、これら 3 台のマシンの負荷平均が非常に低いことがわかります。これら 3 つの値が 50% や 60% を超えるなど非常に高い場合は、懸念すべき事態です。時間の観点から、CPU 負荷が徐々に増加していることに気付いた場合は、注意する必要があります。 メモリや CPU などの他のパフォーマンス監視ツールの使用は、マインド マップに表示されます。 具体的な使い方については、オンライン障害からJavaの問題箇所を考えるを参照してください。 |
<<: 期限切れのクラウド ポリシーは引き続き使用できますか?
>>: ブロックチェーンの破壊力4: 分散型インテリジェントID認証システム
最近、Baidu プロモーションでどれだけのお金が無駄になっているかという不満をよく耳にします。もち...
ハイブリッド クラウド アーキテクチャを構築および作成すると、クラウド コンピューティング サービス...
私は長い間SEOを勉強していなかったため、この技術に関する知識が徐々に後退していました。最近、広州写...
現在、ミルクティーは若者にとって通常の消費財の一つとなり、彼らの特定の社会的ニーズを運び、オフィスで...
以下は私が作成したウェブサイト最適化計画です。参考までに! ****ウェブサイトモール最適化プランウ...
WeChatは最近再び話題になっており、議論の焦点は主にWeChatの製品特性に集中しています。 W...
現在、インターネットショッピングは人々の生活に大きな利便性をもたらし、オンラインショッピングブームを...
Amazon Web Services は、世界的な IT サービスおよびコンサルティング企業である...
現在、インターネットは急速に発展しており、インターネットユーザー数は急増しており、オンラインマーケテ...
[[338420]]この記事はWeChatの公開アカウント「Xianzao Classroom」から...
マイクロ データ センターは、ネットワークのエッジや、ユーザーが集中的なコンピューティング能力を必要...
自動車小売業界では、デジタルモール、AI+小売、自動車スーパーマーケット、オムニチャネルマーケティン...
人々は技術的な変化に対しても不安を抱いています。たとえば、IT プロフェッショナルは、アナログ電話サ...
サーバーレスは、将来のソフトウェア開発のモデルとプロセスを変えています。サーバーレスが未来だと言う人...
2年前、大規模な不動産フォーラムで潘軍氏は網易不動産に「アップルから学びたい」と語った。一昨年、彼は...