ごみ回収GCについて


まず、次のコードを参照してください.
Objet obj = new Object();  

変数objは1つのメモリであり、new Object()は別のメモリであり、このとき、変数objに対応するメモリに格納される数値は、オブジェクトが占有するメモリのヘッダアドレス(すなわち、オブジェクトの参照が保存されている)である.変数はスタックに格納され、オブジェクトはスタックに格納される.したがって、上記の文では、obj変数はスタックに格納され、Objectオブジェクトの参照(Objectのスタックにおけるヘッダアドレス)が格納され、new Object()スタックにObjectというオブジェクトが格納されているメモリを申請しました.
 
変数は一般的に生存時間がある.例えば,1つのメソッド内の変数はこのメソッド内にしか存在しない,すなわちこのメソッドが出ると,この変数は存在しない.では、質問です.変数が存在しない場合、参照されるオブジェクトは実際には存在しないが、オブジェクトはスタック上に存在する.C++変数が無効になる前にプログラマがスタックに格納されているオブジェクトを解放するように要求する(構造解析関数でdeleteを利用).JAVAでは、プログラマーにスタック上のオブジェクトを無視するように要求しています.つまり、プログラマー自身がスタック上のオブジェクトを解放する必要はありません.これらの不要なオブジェクトはゴミのようなものです.では、メモリをきれいに保つために清掃員が一人もいませんか?答えはあります.それはゴミ回収器(GC)です.
 
もちろん、GCはずっと運行されているわけではありません.清掃員が一日24時間街を掃除しないように.ごみ回収作業は、スタックが満タンになるまで開始されません.GCがゴミ回収を開始すると、処理する最初の問題は、ゴミではないゴミを判断することです.一般的には、次の2つのアルゴリズムがあります.
リファレンスカウントアルゴリズム(Reference Counting)最初の考え方は、多くの教科書が対象が生きているかどうかを判断するアルゴリズムでもあります.対象に参照カウンタを追加し、ある場所で参照するとカウンタに1を追加し、参照が失効するとカウンタが1を減らし、いつでもカウンタが0の対象は使用できません.客観的には、参照カウントアルゴリズム実現は簡単で、判定効率が高く、ほとんどの場合、良いアルゴリズムであるが、参照カウントアルゴリズムはオブジェクトの循環参照の問題を解決できない.簡単な例を挙げると、オブジェクトAとオブジェクトBにはそれぞれフィールドb、aがあり、A.b=BとB.a=Aとされている.それ以外にこの2つのオブジェクトには何の参照もない.実際にはこの2つのオブジェクトはアクセスできないが、参照カウントアルゴリズムは彼らを回収できない.ルートサーチアルゴリズム(GC Roots Tracing)は、実際に生産された言語(Java、C#、さらには前述のLispを含む)では、ルートサーチアルゴリズムを使用してオブジェクトが生存しているか否かを判定する.アルゴリズムの基本構想は、「GC Roots」と呼ばれる一連の点を先頭として下方向にサーチし、オブジェクトがGC Rootsに参照チェーンがない場合(Reference Chain)が接続されている場合、このオブジェクトは使用できないことが証明されます.Java言語では、GC Rootsには、1.VMスタック(フレーム内のローカル変数)での参照2.メソッド領域での静的参照3.JNIが含まれます.(一般的なNativeメソッド)での参照生存か死亡か?1つのオブジェクトの死亡を判定するには、少なくとも2回のタグ処理が行われる:オブジェクトがルート検索を行った後、GC Rootsに接続されていない参照チェーンが発見された場合、それは最初にタグされ、少し後に彼のfinalize()メソッド(もしあれば)が実行される.ここでいわゆる「実行」仮想機会がこのメソッドをトリガーすることを意味しますが、実行が終了するまで待つことは約束されていません.この点は必須です.そうしないと、オブジェクトがfinalize()メソッドでゆっくりと実行され、デッドサイクルなどがシステム全体をクラッシュさせやすくなります.finalize()メソッドはオブジェクトが最後に死の運命から逃れる機会であり,後でGCは2回目の規模のやや小さいマーキングを行う.finalize()でオブジェクトが自分を救うことに成功した場合(GC Rootsへの接続を再確立すればよい、例えば参照に自分を付与すればよい)、2回目のタグでは「回収」される集合が除去され、オブジェクトが逃げていない場合、基本的には本当に死から遠くない.特に説明する必要があるのは、ここでfinalize()方法の記述は少し悲しい芸術加工をもたらす可能性があり、筆者がこの方法を使って対象を救うことを奨励しているわけではない.逆に、C/C++の構造関数ではなく、実行コストが高く、不確実性が高く、各オブジェクトの呼び出し順序を保証できないので、できるだけ避けることをお勧めします.外部リソースを閉じるなど、基本的にはtry-finallyを使ったほうがいいです.     
ごみの対象が特定されたらGCでごみ回収が開始されます.
 
        1.「タグクリアアルゴリズム」(Mark-Sweep)
私たちはマーク番号がゴミであると、回収する必要があるすべてのオブジェクトを回収することができます.これがタグ--クリーンアップアルゴリズムで、その主な欠点は2つあります.1つは効率の問題で、タグとクリーンアップの2つのプロセスの効率は高くありません.2つは空間の問題です.タグのクリーンアップ後、大量の不連続なメモリの破片が発生します.空間の破片が多すぎると、後続の使用中に十分な連続メモリが見つからず、別のゴミ収集動作を早期にトリガーする可能性があります.       2.コピー収集アルゴリズム
効率の問題を解決するために、レプリケーションと呼ばれる(Copying)の収集アルゴリズムが現れ、利用可能なメモリを2つに分割し、その中の1つだけを使用し、半領域のメモリが切れた場合、生存しているオブジェクトだけを別のブロックにコピーし、元のメモリ空間全体を一度にクリーンアップします.これにより、メモリ回収のたびに半領域全体の回収になり、メモリ割り当ての際にメモリの割れを考慮する必要がなくなります.チップなど複雑な場合は、スタックトップポインタを移動し、メモリを順番に割り当てるだけで簡単で、実行効率が高い.ただ、このアルゴリズムの代価はメモリを元の半分に縮小することであり、高すぎるのではないでしょうか.       3.Mark-Compactアルゴリズム
レプリケーション収集アルゴリズムは,オブジェクトの生存率が高い場合に効率が低下する.さらに重要なのは、50%のスペースを無駄にしたくない場合、半領域メモリ内のすべてのオブジェクトが100%生存する極端な状況に対処するために追加のスペースを割り当てる必要があるため、古い世代ではこのアルゴリズムを直接選択することは一般的ではありません.そこで、マークプロセスは依然として同じであるが、後続のステップは直接クリーンアップではなく、生存しているすべてのオブジェクトの一端を移動させ、端境界以外のメモリを直接クリーンアップする「マーク・クリーンアップ」(Mark-Compact)アルゴリズムが提案されている.
      
       4.世代別収集アルゴリズム
現在、ビジネス仮想マシンのゴミ収集には「世代別収集」が採用されています.(Generational Collecting)アルゴリズムです.このアルゴリズムには新しいアイデアはありませんが、対象によってメモリをいくつかのブロックに分割します.一般的にはJavaスタックを新生代と旧年代に分けています.このように各年代の特徴に基づいて最適な収集アルゴリズムを採用することができます.例えば、新生代はGCごとに多くの対象が死んで、少量しか生存していません.レプリケーションアルゴリズムを選択すると、生存オブジェクトのレプリケーションコストを最小限に抑えるだけで収集が完了します.