1. Java メモリ モデル
Java 仮想マシンは、プログラムを実行するときに管理するメモリをいくつかのデータ領域に分割します。これらのデータ領域の分布は次の図のようになります。
プログラム カウンター: 小さい。メモリ領域、現在実行されているバイトコードを指します。スレッドが Java メソッドを実行している場合、このカウンタは実行中の仮想マシンのバイトコード命令のアドレスを記録します。ネイティブ メソッドが実行されている場合、このカウンタ値は空になります。
Java 仮想マシン スタック: スレッドはプライベートであり、そのライフサイクルはスレッドと一致します。各メソッドが実行されると、ローカル変数テーブル、オペランド スタック、ダイナミック リンク、メソッド出口などの情報を保存するためのスタック フレームが作成されます。
ネイティブ メソッド スタック: この機能は仮想マシン スタックと似ていますが、仮想マシン スタックは仮想マシンに Java メソッドを実行させる役割を果たし、ローカル メソッド スタックは使用されるネイティブ メソッドを提供する点が異なります。
Java ヒープ: 仮想マシンによって管理される最大のメモリ部分であり、すべてのスレッドによって共有されるこの領域は、ほとんどすべてのオブジェクトがこの領域に割り当てられます。 Java ヒープはメモリ リサイクルの主要な領域であり、ほとんどの現在のコレクタは世代別コレクション アルゴリズムを使用するため、さらに細分化すると、新しい世代と古い世代に分割することもできます。 Eden スペース、From Survivor スペース、To Survivor スペースなどに分けることができます。 Java 仮想マシンの仕様によれば、Java ヒープは、論理的に連続している限り、物理的に不連続な空間にあってもかまいません。
メソッド領域: Java と同様に、各スレッドによって共有され、仮想マシンによってロードされたクラス情報、定常ライト、静的変数、ジャストインタイム コンパイラによってコンパイルされたコードなどのデータを保存するために使用されます。
ランタイム定数プール。ランタイム定数プールは、クラスのバージョン、フィールド、メソッド、インターフェイスなどの記述情報に加えて、使用される定数プールもあります。コンパイル中に生成されたさまざまなリテラルおよびシンボリック参照を保存します。操作中に新しい定数を定数プールに入れることができます。最も一般的に使用されるメソッドは、String クラスの intern() メソッドです。String インスタンスが intern を呼び出すと、Java は定数プール内で同じ Unicode 文字列定数を検索します。そうでない場合は、その参照を返します。定数プール内のインスタンス文字列と等しい Unicode 文字列を追加し、その参照を返します。
2. ガベージ オブジェクトを特定する方法
Java ヒープにはいくつかのオブジェクト インスタンスが保存されています。ガベージ コレクターがヒープをリサイクルする前に、どのオブジェクトがまだ「生きている」のか、どのオブジェクトが「死んでいる」のかをまず判断する必要があります。決して使用されないオブジェクトです。
参照カウント方法
参照カウント方法は実装が簡単で、ほとんどの場合に優れたアルゴリズムです。原理は、オブジェクトに参照カウンタを追加することです。オブジェクトへの参照があるたびに、カウンタは 1 ずつ増加します。参照が期限切れになると、カウンタは 1 ずつ減少します。カウンタ値が 0 の場合、オブジェクトはそのオブジェクトに存在しないことを意味します。はもう使用されていません。参照カウント方式はオブジェクト間の循環参照の問題を解決するのが難しいことに注意してください。主流の Java 仮想マシンはメモリ管理に参照カウント方式を使用しません。
到達可能性分析アルゴリズム
このアルゴリズムの基本的な考え方は、「GC ルート」と呼ばれる一連のオブジェクトを開始点として使用し、これらのノードから下方向に検索することです。検索によって移動されるパスは参照チェーンと呼ばれます。 ( 参照チェーン)、オブジェクトに GC ルートに接続された参照チェーンがない (グラフ理論の用語では、GC ルートからこのオブジェクトに到達できない) 場合、このオブジェクトが利用できないことがわかります。図に示すように、オブジェクト object 5、object 6、object 7 は相互に関連していますが、GC ルートから到達できないため、リサイクル可能なオブジェクトとして判断されます。
Java 言語では、GC ルートとして使用できるオブジェクトには次のものがあります:
仮想マシン スタック (スタック フレーム内のローカル変数テーブル) で参照されるオブジェクト。
メソッド領域のクラス静的プロパティによって参照されるオブジェクト。
メソッド領域の定数によって参照されるオブジェクト。
ローカル メソッド スタック内の JNI (一般的に言えばネイティブ メソッド) によって参照されるオブジェクト。
ここで疑問が生じますが、到達可能性分析アルゴリズムはオブジェクト間の循環参照の問題を引き起こすのでしょうか?答えは「はい」です。つまり、オブジェクト間の循環参照の問題は発生しません。 GC ルートは、オブジェクト グラフの外側で特別に定義された「開始点」であり、オブジェクト グラフ内のオブジェクトから参照することはできません。
死ぬか死なないか
到達可能性分析アルゴリズムの到達不可能なオブジェクトが「停止する必要がない」場合でも、オブジェクトは一時的に「保護観察」段階にあり、オブジェクトが実際に停止していると宣言するには、少なくとも 2 つのマーキング プロセスを通過する必要があります。オブジェクトの分析により、GC ルートに接続されている参照チェーンが存在しないことが判明した場合、初めてマークされ、フィルター処理されます。フィルター条件は、このオブジェクトに対して finapze() メソッドを実行する必要があるかどうかです。オブジェクトが finapze() メソッドをカバーしていない場合、または finapze() メソッドが仮想マシンによって呼び出された場合、仮想マシンは両方の状況を「実行する必要がない」ものとして扱います。プログラムは finapze() をオーバーライドすることで「スリリングな」自己救済プロセスを実行できますが、そのチャンスは 1 回だけです。
/** * 此代码演示了两点: * 1.对象可以在被GC时自我拯救。 * 2.这种自救的机会只有一次,因为一个对象的finapze()方法最多只会被系统自动调用一次 * @author zzm */ pubpc class FinapzeEscapeGC { pubpc static FinapzeEscapeGC SAVE_HOOK = null; pubpc void isApve() { System.out.println("yes, i am still apve :)"); } @Override protected void finapze() throws Throwable { super.finapze(); System.out.println("finapze mehtod executed!"); FinapzeEscapeGC.SAVE_HOOK = this; } pubpc static void main(String[] args) throws Throwable { SAVE_HOOK = new FinapzeEscapeGC(); //对象第一次成功拯救自己 SAVE_HOOK = null; System.gc(); //因为finapze方法优先级很低,所以暂停0.5秒以等待它 Thread.sleep(500); if (SAVE_HOOK != null) { SAVE_HOOK.isApve(); } else { System.out.println("no, i am dead :("); } //下面这段代码与上面的完全相同,但是这次自救却失败了 SAVE_HOOK = null; System.gc(); //因为finapze方法优先级很低,所以暂停0.5秒以等待它 Thread.sleep(500); if (SAVE_HOOK != null) { SAVE_HOOK.isApve(); } else { System.out.println("no, i am dead :("); } } }
実行結果は次のとおりです:
finapze mehtod executed! yes, i am still apve :) no, i am dead :(
それでは参照について話しましょう
参照カウントアルゴリズムを通じてオブジェクトへの参照の数を判断するのか、それとも到達可能性分析を通じてオブジェクトの参照チェーンに到達可能かどうかを判断するのかオブジェクトが生きているかどうかを判断するアルゴリズムは、「Quote」に関連しています。 JDK 1.2 より前は、Java における参照の定義は非常に伝統的なものでした。参照型データに格納されている値が別のメモリの開始アドレスを表す場合、そのメモリは参照を表すと言われます。 JDK 1.2 以降、Java は参照の概念を拡張し、参照を強参照、ソフト参照、弱参照、ファントム参照の 4 つのタイプに分けました。これら 4 つは引用の強度を徐々に弱めます。
• 強参照とは、「Object obj = new Object()」など、プログラム コード内で遍在する参照を指します。強参照が存在する限り、ガベージ コレクターは参照されたオブジェクトをリサイクルしません。
• ソフト参照は、便利ではあるが必須ではないいくつかのオブジェクトを記述するために使用されます。ソフト参照に関連付けられたオブジェクトの場合、これらのオブジェクトは、システムでメモリ オーバーフロー例外が発生する前の 2 回目のリサイクルのリサイクル スコープに含まれます。このリサイクルに十分なメモリがない場合、メモリ オーバーフロー例外がスローされます。 JDK 1.2 以降では、ソフト参照を実装するために SoftReference クラスが提供されています。
• 弱い参照は、必須ではないオブジェクトを記述するためにも使用されますが、その強度はソフト参照よりも弱く、弱い参照に関連付けられたオブジェクトは、次のガベージ コレクションが発生するまでしか存続できません。ガベージ コレクターが動作すると、現在のメモリが十分であるかどうかに関係なく、弱い参照のみに関連付けられたオブジェクトがリサイクルされます。 JDK 1.2 以降では、弱参照を実装するために WeakReference クラスが提供されています。
• ファントム参照は、ゴースト参照またはファントム参照とも呼ばれ、最も弱い種類の参照関係です。オブジェクトに仮想参照があるかどうかはその存続期間には影響せず、仮想参照を通じてオブジェクト インスタンスを取得することは不可能です。オブジェクトの仮想参照関連付けを設定する唯一の目的は、オブジェクトがコレクターによって再利用されたときにシステム通知を受け取ることです。 JDK 1.2 以降では、仮想参照を実装するために PhantomReference クラスが提供されています。
ソフトリファレンスの使用例:
package jvm; import java.lang.ref.SoftReference; class Node { pubpc String msg = ""; } pubpc class Hello { pubpc static void main(String[] args) { Node node1 = new Node(); // 强引用 node1.msg = "node1"; SoftReference<Node> node2 = new SoftReference<Node>(node1); // 软引用 node2.get().msg = "node2"; System.out.println(node1.msg); System.out.println(node2.get().msg); } }
出力結果は次のとおりです:
node2 node2
3. 典型的なガベージコレクションアルゴリズム
1. 最も基本的なガベージコレクションアルゴリズムです。それが最も基本的であると言われる理由は、それが実装が最も簡単であり、アイデアが最も単純であるためです。マークスイープ アルゴリズムは、マーク フェーズとクリア フェーズの 2 つのフェーズに分かれています。マーキング フェーズのタスクは、リサイクルする必要があるすべてのオブジェクトにマークを付けることです。クリア フェーズは、マークされたオブジェクトが占めていたスペースを回復することです。具体的なプロセスを以下の図に示します。
この図から、マーククリアアルゴリズムは実装が比較的簡単であることが簡単にわかりますが、メモリの断片化が起こりやすいという深刻な問題があります。多くのフラグメントが後続のプロセスを引き起こす可能性がある 大きなオブジェクトにスペースを割り当てる必要がある場合、十分なスペースが見つからず、新しいガベージ コレクション アクションが事前にトリガーされます。
2. コピーアルゴリズム
マークスイープアルゴリズムの欠点を解決するために、コピーアルゴリズムが提案されました。利用可能なメモリを容量に応じて 2 つの同じサイズのブロックに分割し、一度にそのうちの 1 つだけを使用します。このメモリ ブロックが使い果たされたら、残っているオブジェクトを別のブロックにコピーし、使用されているメモリ領域を一度にクリーンアップすることで、メモリの断片化の問題が発生する可能性が低くなります。具体的なプロセスを以下の図に示します。
このアルゴリズムは実装が簡単で、動作が効率的で、メモリの断片化が起こりにくいですが、使用可能なメモリが減少するため、メモリ空間の使用に高いコストがかかります。の元の半分に。
明らかに、コピー アルゴリズムの効率は、生き残るオブジェクトの数と大きな関係があります。多くのオブジェクトが生き残ると、コピー アルゴリズムの効率は大幅に低下します。
3. マークコンパクトアルゴリズム
Copying アルゴリズムの欠点を解決し、メモリ空間を最大限に活用するために、Mark-Compact アルゴリズムが提案されています。このアルゴリズムのマーキング フェーズはマーク スイープと同じですが、マーキングの完了後、リサイクル可能なオブジェクトを直接クリーンアップするのではなく、生き残ったオブジェクトを一方の端に移動し、その後、端の境界の外側のメモリをクリーンアップします。具体的なプロセスを以下の図に示します。
4. Generational Collection (世代別コレクション) アルゴリズム
世代別コレクション アルゴリズムは、現在ほとんどの JVM ガベージ コレクターで使用されているアルゴリズムです。その中心となるアイデアは、オブジェクトのライフサイクルに従ってメモリをいくつかの異なる領域に分割することです。通常の状況では、ヒープ領域は Tenured Generation と Young Generation に分割されます。Old Generation の特徴は、ガベージ コレクションごとに少数のオブジェクトのみをリサイクルする必要があることです。一方、Young Generation の特徴は、ガベージコレクションごとにリサイクルが必要なオブジェクトが多数あるため、世代ごとの特性に応じて最適な収集アルゴリズムを採用できます。
現在、ほとんどのガベージ コレクターは新世代のコピー アルゴリズムを採用しています。これは、ほとんどのオブジェクトが新世代の各ガベージ コレクションでリサイクルされる必要があるためです。つまり、コピー操作の数は少なくなりますが、実際には 1 に続きません。 : 新世代の空間は 1 の比率で分割されます。一般的に、新世代は、より大きな Eden 空間と 2 つの小さな Survivor 空間に分割されます (通常、Eden 空間とリサイクル時は 8:1:1)。 Survivor スペースの 1 つで、Eden と Survivor に残っているオブジェクトを別の Survivor スペースにコピーしてから、Eden と使用したばかりの Survivor スペースをクリーンアップします。
古い世代の特徴は、毎回少数のオブジェクトのみがリサイクルされることであるため、一般的に Mark-Compact アルゴリズムが使用されます。
上記の Java メモリ モデルとガベージ コレクションの簡単な分析は、編集者が共有したすべての内容です。参考にしていただければ幸いです。また、PHP 中国語 Web サイトをサポートしていただければ幸いです。
Java メモリ モデルとガベージ コレクションに関するその他の記事については、PHP 中国語 Web サイトに注目してください。