JAVAベースのゴミ回収器とfinalize()

5240 ワード

JAvaのゴミ回収器は、不要なオブジェクトが占有するメモリリソースを回収し、プログラマーが手動で解放する必要がなくなり、「メモリ漏れ」のリスクを回避します.しかし、クリーンアップ作業をゴミ回収器に任せるのは万全ではありません.ゴミ回収器はnewによって作成されたオブジェクトだけをクリーンアップします.もしあなたのオブジェクトが「特殊」なメモリリソースを獲得したとしたら(newを使用していない)、ゴミ回収器はこのリソースを解放しません.
このような状況に対応するためにjavaは、クラス内でfinalize()というメソッドを定義することを許可します.このメソッドは、ゴミ回収器がオブジェクトが占有するストレージ領域を解放する準備ができたら、まずfinalize()メソッドを呼び出し、次のゴミ回収動作が発生すると、オブジェクトが占有するメモリを本当に回収することができます.したがってfinalize()を使用して、上記の「特殊」リソースをクリーンアップできます.
ここにはいくつかの注意事項があります.
1:対象がゴミ回収されない場合
ごみ回収は常に発生するものではなく,プログラムがストレージスペースの使い切りに瀕しているときにのみ,オブジェクトが占有するスペースが解放される.プログラムが終了するまでゴミ回収機がオブジェクトのストレージスペースを解放しない場合、プログラムが終了するにつれて、それらのリソースもすべてオペレーティングシステムに返されます.
2:ゴミ回収はメモリのみ
ごみ回収器を使用する唯一の理由は、プログラムが使用しなくなったメモリを回収するためであるため、ごみ回収に関連する任意の動作(特にfinalize()メソッド)についても、メモリおよびその回収に関連する必要があります.JAvaではすべてオブジェクト(new()によって作成)ですが、finalize()によってメモリをクリーンアップする必要がある特別な状況は何ですか?この特殊な状況は主に「ローカルメソッド」を使用する場合に発生し、ローカルメソッドはjavaでjava以外のコードを呼び出す方法であり、例えばCとC++である.Java以外のコードでは、メモリを割り当てるためにCのmalloc()関数が呼び出される可能性があります.ローカルメソッドを含むオブジェクトが回収されても、このリソースは常に占有され、メモリが漏洩するため、finalize()でCのfree()を呼び出す必要があります.もちろん、ローカルメソッドで呼び出されます.
したがってfinalize()は通常のクリーンアップ方法として使用するのではなく、メモリをクリーンアップするためにのみ使用する必要があります.他のクリーンアップ作業では、プログラマは通常の方法を作成し、適切な時点で明確に呼び出す必要があります.
finalize()については、「オブジェクトの終了条件の検証」という一般的な使い方もあります.たとえば、次のようになります.
class Book{
    boolean checkOut = false;

    Book(boolean checkOut){
        this.checkOut = checkOut;
    }

    void checkIn(){
        checkOut = false;
    }

    protected void finalize(){
        if(checkOut){
            System.out.println("Error");
        }
    }
}

public class Test{
    public static void main(String[] args){
        Book book1 = new Book(true);
        book1.checkIn();

        new Book(true);
        System.gc(); //          
    }
}

本例の終了条件は,すべてのBookオブジェクトがゴミ回収として扱われる前にチェックインされるべきであったが,main()ではプログラマーのミスで1冊の本がチェックインされなかった.finalize()が終端条件を検証しなければ,この欠陥を発見することは困難である.
では、ゴミ回収機はどのように動いているのでしょうか.
一般に、スタックにオブジェクトを割り当てる速度は比較的遅く、Javaのすべてのオブジェクト(基本タイプを除く)はスタックに割り当てられています.javaはゴミ回収器を通じて、オブジェクトの作成速度を効果的に向上させ、javaがスタックから空間を割り当てる速度は、他の言語がスタックから空間を割り当てる速度に匹敵します.例えば、C++のスタックを「庭」と想像することができます.、中の各オブジェクトは自分の地盤を管理し、しばらくするとオブジェクトが破棄される可能性がありますが、地盤は再利用されなければなりません.C++はオブジェクトを作成するのにほとんどの時間を費やします.Javaはまったく異なり、オブジェクトを割り当てるたびに1つのコンベアに似ています.コンベアは前に1つ移動します.javaの「スタックポインタ」は、割り当てられていない領域に簡単に移動するだけで、オブジェクトのストレージスペースの割り当て速度が非常に速いことを意味します.
しかしjavaのスタックは完全にコンベアのように動作しているわけではありません.そうすれば、メモリリソースはすぐに消費されます.その秘密はごみ回収器の介入にあり,それが作動すると,回収空間を回収しながらスタック中のオブジェクトをコンパクトに配列させる.このように「スタックポインタ」はコンベアの先頭に移動しやすく、ゴミ回収器によってオブジェクトを並べ替え、高速で無限の空間が割り当てられるスタックモデルを実現します.
そしてゴミ回収のメカニズムを理解します.
1つ目は参照カウントと呼ばれ、このカウントは簡単だが速度が遅い.各オブジェクトには、参照がオブジェクトに接続されている場合、参照カウント+1が含まれ、参照が役割ドメインから離れたりnullに設定されている場合、参照カウント-1が含まれます.ごみ回収器は、すべてのオブジェクトを含むリストを巡回し、オブジェクトの参照数が0であることが判明すると、その占有スペースを解放します.しかし、この方法には欠陥があり、オブジェクト間に循環参照がある場合、「オブジェクトは回収されるべきだが、参照数は0ではない」という状況が発生する可能性があります.例:
public class GcDemo {

    public static void main(String[] args) {
        GcObject obj1 = new GcObject(); //Step 1:   1      1
        GcObject obj2 = new GcObject(); //Step 2:   2      1

        obj1.instance = obj2; //Step 3:   2     +1=2
        obj2.instance = obj1; //Step 4:   1     +1=2

        obj1 = null; //Step 5:  1     -1=1
        obj2 = null; //Step 6:  2     -1=1
    }
}

class GcObject{
    public Object instance = null;
}

上記の例では,2つのオブジェクトは既にゴミであるが,参照カウントは1である.このようなクリーンアップ方式を使ったjava仮想マシンはありません.
いくつかのより速い方法では、参照カウントに依存しません.これらの根拠は、スタックまたは静的ストレージ領域に存在する参照に最終的に遡ることができる任意の「アクティブ」オブジェクトについてです.この参照チェーンは、複数のオブジェクト階層を通過する可能性があります.これにより、スタックと静的ストレージ領域からすべてのリファレンスを巡回すると、すべての「アクティブ」オブジェクトが見つかります.検出された各参照については、必ず参照されたオブジェクトを遡り、次にこのオブジェクトに含まれる参照が繰り返され、「スタックおよび静的ストレージ領域に根ざした参照」によって形成されたネットワークがすべてアクセスされるまで、検出されていないすべてのオブジェクトが自動的に回収されます.
このようにjava仮想マシンは適応的なゴミ回収技術を採用している.停止というモードがあります.レプリケーション:プログラムの実行を一時停止し、現在のスタックから別のスタックにすべての生存オブジェクトをレプリケーションすると、古いスタックをゴミとしてクリーンアップできます.オブジェクトが新しいスタックにレプリケーションされると、コンパクトに並べられます.これにより、以前の方法で簡単に、直接、迅速に新しい空間を割り当てることができます.この方法にも問題があります.オブジェクトが1つの場所から別の場所に移動すると、その参照を指すすべての参照が修正されなければなりません.また、オブジェクトに含まれる参照もいくつかあります.遍歴の過程でしか見つからないので、効率が低くなり、オブジェクトを2つのスタックで逆さまにして、実際に必要とされる2倍以上の空間を維持することができます.このJava仮想マシンのいくつかの方法は、スタックから比較的大きなメモリを必要に応じていくつか割り当て、これらの大きなメモリの間でレプリケーション動作が発生することです.2つ目の問題は、プログラムが安定した状態になると、少量のゴミしか発生しない可能性があり、ゴミも発生しないことです.それでも、回収器はすべてのオブジェクトを1つの場所から別の場所にコピーします.これは無駄です.このような状況を避けるために、javaの仮想機会をチェックし、新しいゴミが発生しなければ、別のモードに変換します.
このモードはマーク-清掃と呼ばれ、一般的な用途では速度がかなり遅いが、少量のゴミしか発生せず、ゴミも発生しないことを知っていると、速度が速くなる.タグ-清掃の考え方は、スタックと静的ストレージ領域から出発し、すべての参照を遍歴し、生存するすべてのオブジェクトを見つけることです.生きているオブジェクトを見つけるたびに、オブジェクトにタグが設定され、すべてのタグが完了すると、クリーンアップ動作が開始されます.クリーンアップ中、マークされていないオブジェクトは解放され、コピー動作は発生しないため、残りのスタックスペースは不連続であり、ゴミ回収器は連続スペースを望んでおり、残りのオブジェクトを再整理する必要があります(このステップは時間がかかります).
前述したように、クリーンアップ効率の高いjava仮想マシンでは、メモリ割り当ては比較的大きな「ブロック」単位で行われ、オブジェクトが大きい場合は個別のブロックを占有します.ブロックがあれば、ゴミ回収器は廃棄されたブロックに対象をコピーすることができます.各ブロックには、生存しているかどうかを記録するために対応する代数があり、ブロックがどこかで参照されると、その代数が増加します.ゴミ回収器はクリーンアップ動作を実行する際に代数0のブロックが廃棄され、大きなオブジェクトはコピーされません(個別のブロックです).小さなオブジェクトを含むブロックはコピーされて整理されます.javaの仮想機会を監視し、すべてのオブジェクトが安定し、ゴミ回収機の効率が低下すると、タグ-清掃モードに切り替えます.(クリーンアップのみ、整理しない).同様にjava仮想機会はタグクリーンアップの効果を追跡し、スタックスペースに多くの破片が発生すると、停止-コピー方式に切り替わる.これが適応ゴミ回収技術である.