blog

JVMランタイム・データ領域の詳細分析

Javaプログラムは実行時にさまざまな種類のデータを生成し、この実行時データはJVMの実行時データ領域に格納されます。JVMの仕様によると、メモリはプログラム・カウンタ、仮想マシン・スタック、ローカル...

Sep 14, 2020 · 7 min. read
シェア

ランタイムデータ領域

Javaプログラムは実行時にさまざまな種類のデータを生成し、この実行時データはJVMの実行時データ領域に格納されます。JVMの仕様によると、メモリは プログラム・カウンタ、仮想マシン・スタック、ローカル・メソッド・スタック、ヒープ、メソッド領域の 5つの部分に分割されます。

プログラムカウンタ

プログラム・カウンタはPCレジスタとも呼ばれ、命令関連の情報を格納します。メモリの非常に小さな領域で、他のデータ領域と比較して最も高速に実行される領域です。主な機能は、実行エンジンによってフェッチされ、実行れる次の命令を指すアドレスを格納することです。

Javaはマルチスレッド実行をサポートしていますが、CPUは同時に1つのスレッドしか実行できないため、CPUはタイムスライスを常に切り替えることで複数のスレッドの実行を制御し、スレッドが実行されたタイムスライスを元に戻して実行を継続するようにプログラムカウンタを使用します。

プログラムカウンターの特徴

  • スレッドはプライベートで、各スレッドは独立したプログラム・カウンターを持ち、ライフサイクルはスレッドのライフサイクルと同じです。
  • Javaメソッドが実行された場合は、仮想マシンによって実行されているバイトコード・ファイル命令のアドレスが保存され、Nativeメソッドが実行された場合は、プログラム・タイマーが空になります。
  • はVMのランタイム・データゾーンでOutOfMemoryErrorが発生しない唯一のメモリ領域です。

仮想マシンスタック

仮想マシン・スタックは、Javaメソッド実行のメモリ・モデルを記述するために使用され、メソッドの各実行は、仮想マシン・スタックにスタック・フレームを生成します。

仮想マシンスタックの特徴

  • VMスタックはスタック・ベースのデータ構造で、インスタックとアウトスタックの2つのオペレーションがあります。
  • 仮想マシン・スタックはGCされません。

スタックはデータ構造に属し、仮想マシンのスタックのメモリサイズには2つのモデルがあり、1つは動的モデル、もう1つは固定サイズモデルです;

ダイナミック・モード:VMスタックがダイナミック・モードを使用して深く設定されている場合、メソッドの実行時に十分なメモリ・サイズが要求されなかったり、新しいスレッドの作成時に新しいスタック・フレームを作成するのに十分なサイズがなかったりすると、OutOfMemoryError例外がスローされます。

固定サイズ:VMがスタックの深さを設定するために固定サイズ・モードを使用する場合、スレッドがスタックに設定された最大容量よりも多くのメモリ領域の割り当てを要求すると、StackOverflowError例外がスローされます。固定サイズは -Xss パラメータで設定できます。

スタックフレーム

スタックフレームは仮想マシンのスタックにおけるストレージの基本単位であり、各スタックフレームはスレッド上で実行されるメソッドに対応し、実行中のメソッドに関する様々なデータ情報を保持します。

スタック・フレームは実際には、ローカル変数のテーブル、オペランド・スタック、ダイナミック・リンク、メソッドのリターン・アドレスなどを含むデータセットです。VMスタックでは、メソッドの実行はスタック・フレームをVMスタックに押し込むことに対応し、メソッドのリターンはスタック・フレームの終了に対応します。

ローカル変数テーブル:メソッドの入力パラメータを格納する配列として定義され、基本データ型、オブジェクト参照、リターンアドレス型を含むメソッド内部で定義されたローカル変数。long、double型のデータは2つのローカル変数を占有します。

ローカル変数テーブルのサイズとメモリ割り当てはコンパイラ内で行われ、プログラム実行中にローカル変数テーブルのサイズが変わることはありません。

ローカル変数テーブルの基本的な記憶単位はスロットで、前述のローカル変数と解釈でき、32ビットデータ型では1スロット、64ビットデータ型では2スロットを占有します。

ローカル変数テーブルでは、仮想マシンはインデックス位置でローカル変数テーブルの変数をアクセスします。そのインデックスは0からローカル変数テーブルの最大スロット数までです。インデックスアクセスでは、それが32ビットのデータである場合、そのインデックスはnであり、64ビットのデータのために、そのインデックス位置はn、n + 1 2スロットです。".

スタックフレームのスロットは再利用可能であり、ローカル変数テーブルの変数も、ローカル変数テーブルで直接または間接的に参照されるオブジェクトがリサイクルされない限り、ゴミ収集のGC ROOTノードです。

オペランドスタック: 主に計算プロセスの中間結果を保存するために使用され、計算プロセス中の一時的な変数のためのストレージスペースとして、オペランドスタックは、メソッド実行のための実際のワークスペースとみなすことができます。また、FIFO スタック構造でもあります。メソッドの実行中、データはバイトコード命令に従ってオペランドスタックに書き込まれたり、オペランドスタックから取り出されたりします。新しいスタックフレームが作成されるとき、そのオペランドスタックは空です。どのオペランドスタックも、メソッド実行中にデータを格納するための明示的なスタック深度を持ちます。ローカル変数テーブルと同様に、オペランドスタックのサイズはコンパイル時に決定されます。

オペランド・スタックの要素は、どのJavaデータ型でもかまいません。ローカル変数テーブルと同様に、64ビット・データ型は2つのスタック・ユニットを占有します。しかし、ローカル変数テーブルのようにインデックスによってアクセスされることはありません。オペランド・スタックは、データへの標準的なスタック内アクセスとスタック外アクセスによってアクセスされます。

ダイナミック・リンク:ランタイム定数プールへのメソッド参照とも呼ばれます。仮想マシン・スタックのスタック・フレームでは、各スタック・フレームに、実行時定数プール内のスタック・フレームが属するメソッドへの参照が含まれています。

メソッドのリターンアドレス:AメソッドでBメソッドを呼び出すと、Aメソッドに通常のリターンのBメソッドの実行終了時にAメソッドに戻り、リターンアドレスは、実行を続行するには、行のAメソッドに戻るBメソッドの終了後に記録されます。

ローカル・メソッド・スタック

操作中のJavaプログラムは、基礎となるネイティブメソッドの一部を呼び出すことができ、これらのメソッドは、実際にはJavaメソッドと同じですが、唯一の基礎となるC + +言語の実装は、メソッドの実行はまた、実行プロセス中に生成されたいくつかのデータを格納するためのメモリ領域を必要とし、ネイティブメソッドスタックは、これらのネイティブメソッドの呼び出しを格納するために使用されます。ネイティブ・メソッド・スタックは、仮想マシンのスタック構造に似ています。

メソッド

メソッド・エリアには、主に仮想マシンによってロードされたクラスに関する情報(クラス名、メソッド情報、フィールド情報、定数、静的変数、インスタント・コンパイラによってコンパイルされたコード、その他のデータなど)が格納されます。

JDK 1.7以前ではメソッド領域をパーマネントと呼ぶのが一般的でしたが、1.8以降ではパーマネントの代わりにメタスペースが使われ、ローカルメモリに実装されるようになりました;

メタ・スペースは、JVM仕様のメソッド・エリアの実装であるパーマネント生成と本質的に同じですが、両者の違いは、メタ・スペースが、JVM仕様のメソッド・エリアの実装であるパーマネント生成を行わないことです。

メソッド領域は、ヒープと同様に、連続メモリから解放され、固定サイズか動的に拡張されるかを選択でき、ゴミ収集を実装するかどうかを選択できます。メソッド領域でのGCは、主に定数プールの回復と型のアンロードのために行われます。OOMは、メソッド領域がメモリ割り当て要件を満たすことができない場合にスローされます。

ランタイム定数プールはメソッド領域の一部であり、通常はコンパイル期間中に生成されたリテラルとシンボリック参照を保存します。 クラスがロードされると、このデータはクラス定数プールからランタイム定数プールに保存されます;

クラス・ロードに関する前回の記事では、クラスの完了がメソッド領域に格納された後にクラスがロードされる場合の、クラス・ロードの詳細なプロセスについて言及しました。

方法論的領域の特徴:

  • メソッド領域はスレッド共有
  • JVM起動時に作成されるため、実際の物理メモリはヒープと同様にばらばらになります。

ヒープ

Java仮想マシンの実行時データ領域では、ヒープはメモリ空間の最大の部分であり、オブジェクトのインスタンスはヒープメモリに格納されるため、ヒープも仮想マシンの作成と一緒に作成されます。

ゴミの収集方法ですが、現在のゴミ収集機は基本的に分割世代収集アルゴリズムを使用しているため、ヒープ空間は新しい世代と古い世代に細分化されます。

ニュージェネレーションとオールドジェネレーションの比率は1:2で、ニュージェネレーションの中にもエデンゾーン、サリヴァーフロムゾーン、サリヴァートゥゾーンという3つのゾーンがあり、その比率は8:1:1;

ヒープメモリは、スレッドで共有されているので、必然的にオブジェクトを作成するメモリ領域の同じブロック内のヒープ内の複数のスレッドがあるかもしれませんし、ロックするオブジェクトを作成するたびに、それがあまりにも多くのHotSpot仮想マシンの効率に影響を与える場合は、問題を解決するために、edenの各スレッドにオブジェクトを作成するために使用されるプライベート空間の小さな断片を割り当て、この部分は、同時オブジェクト作成メモリの問題を解決するために、この方法で、**TLAB**領域として知られています。領域は、このようにメモリの問題でマルチスレッドオブジェクトの同時作成を解決するために、デフォルトでは、スペースのedemの1パーセントを占有しますが、あなたは大きなオブジェクトの数を作成する場合、このプライベート空間は、直接eden領域または老齢に置くことはできません。

新しく生成されたオブジェクトはeden領域に確保され、eden領域が一杯になるとMonir GCが起動され、生存しているオブジェクトを到達可能性アルゴリズムに従ってマークし、生存しているオブジェクトをS0領域にコピーし、オブジェクトヘッダのサブバンドのageを+1し、eden領域のオブジェクトを全てクリアします。その後、作成されたオブジェクトはedenに格納され続け、edenが再び満杯になると、Monir GCが起動され、edenとS0にあるゴミオブジェクトを同時にクリーンアップします。その後、生き残ったオブジェクトにマークを付けてS1にコピーし、edenとS0をクリーンアップするというように、オブジェクトの帯域のageが15になるまで繰り返し、その後、古いageに移行します。

老年期に入るための条件:

  • 対象者の世代年齢は15
  • 新しく作られた大型のオブジェは、そのまま老齢期に入れることができます。
  • サバイバー・エリアでは、オブジェクトのバッチのサイズがこのサバイバー・メモリの50%より大きい場合、バッチの年齢より古いオブジェクトはそのまま古い年齢に移動します。
Read next

AMA|BocaのDeFiアドバンテージと、Acalaが今後数百万人のユーザーにどのようにサービスを提供するかを探る?

AcalaAMA 7月31日、BKEX総合研究所のプレミアム座談会「ボカエコとDeFiを組み合わせるメリット」に、Acala取締役の陳希亮が招待されました。 このAMAでは、陳希亮がDeFi市場における現在のボトルネック、Acalaの将来について、独自の視点から質問に答えました。

Sep 14, 2020 · 8 min read