JAvaオブジェクト回収

4263 ワード

オブジェクト回収アルゴリズム
リファレンスカウントアルゴリズム
マイクロソフト社のCOM(Component Object Model)技術、ActionScriptを使用したFlashPlayer、Python言語、ゲームスクリプト分野で広く応用されているSquirrelでは、リファレンスカウントアルゴリズムを用いてメモリ管理を行っている.
参照カウントアルゴリズムは、オブジェクトに参照カウンタを追加し、1つの場所が参照されるたびにカウンタ値に1を加算します.参照が無効になると、カウンタ値はそれに応じて1減少します.カウンタ値が0の場合、オブジェクトは使用できなくなり、このオブジェクトを回収します.このアルゴリズムは実現が難しくなく,判定の効率も高いように聞こえるが,オブジェクト間の相互循環参照の問題を解決するのが難しいなどの問題もある.たとえば、class Objのメンバー属性Object instanceのオブジェクトobjAとオブジェクトobjBにはinstanceフィールドがあり、objA.instance = objB;objB.instance = objA;の値を付けて相互参照しますが、それ以外の参照はありません.参照カウントアルゴリズムでは回収されません.実際には、この2つのオブジェクトはアクセスできません.
かのうせいぶんせきアルゴリズム
一部の主流の商用言語(java,C#,Lispなど)の主流実装では,達性分析(Reachability Analysis)によって対象が生存しているか否かを判断すると呼ばれる.
達成性解析アルゴリズムの基本的な考え方は、一連のGC Rootsと呼ばれるオブジェクトを開始点として、これらのノードから下へ検索し、検索した経路を参照チェーン(Reference Chain)と呼び、1つのオブジェクトがGC Rootsに達しても参照チェーンが接続されていない場合、このオブジェクトが利用できないことを証明し、回収することである.
Java言語でGC Rootsとして使用できるオブジェクトは次のとおりです.1.仮想マシンスタック(スタックフレーム内のローカル変数テーブル)で参照されるオブジェクト.2.メソッド領域内のクラス静的属性で参照されるオブジェクト.3.メソッド領域で定数で参照されるオブジェクト.4.ローカルメソッドスタック内のJNI(一般的にNativeメソッド)がオブジェクトを参照する.
オブジェクトの回収
参照タイプ
Javaでの参照は4つに分けられます.
1.強引用(Strong Reference)
強引用とは、プログラムコードに普遍的に存在するObject obj = new Object();のような引用を指し、強引用が存在する限り、ゴミ収集器は参照されたオブジェクトを回収しない.
2.ソフトリファレンス(Soft Reference)
ソフトリファレンスは、まだ役に立つが必要ではないオブジェクトを記述するために使用されます.ソフトリファレンスに関連付けられたオブジェクトについては、メモリオーバーフローが発生する前に、これらのオブジェクトを回収範囲に列挙して2回目の回収を行います.今回の回収で十分なメモリがない場合、メモリオーバーフロー異常が放出されます.ソフトリファレンスを実現するには、SoftReferenceを使用します.
3.弱引用(Weak Reference)
弱いリファレンスも、非必須オブジェクトを記述するために使用されますが、その強度はソフトリファレンスよりも弱く、弱いリファレンスに関連付けられたオブジェクトは、次のゴミ回収前にしか生存できません.ゴミ収集器が動作すると、現在のメモリが十分であるかどうかにかかわらず、弱い参照のみで関連付けられたオブジェクトが回収されます.WeakReferenceクラスを使用すると、弱い参照が実現されます.
4.ダミーリファレンス(Phantom Reference)
虚引用は幽霊引用や幻影引用とも呼ばれ、最も弱い引用関係である.1つのオブジェクトに虚参照が存在するかどうかは、生存時間に影響を与えることはなく、虚引用によってオブジェクトインスタンスを取得することもできません.オブジェクトに虚参照関連付けを設定する唯一の目的は、このオブジェクトがコレクタによって回収されたときにシステム通知を受け取ることです.PhantomReferenceクラスを使用して、ダミー参照を実装します.
生きるか死ぬか
達成性分析アルゴリズムでは達成できないオブジェクトは、すぐに回収されるとは限らない.実際に回収するには、少なくとも2回のマーキングプロセスを経なければならない.オブジェクトが達成性分析を行った後、GC Rootsに接続されていない参照チェーンが発見された場合、オブジェクトは最初にマーキングされ、フィルタリングされる.フィルタリングの条件は、このオブジェクトがfinalize()メソッドを実行する必要があるかどうかです.オブジェクトがfinalize()メソッドを上書きしていない場合、またはfinalize()メソッドが仮想マシンによって呼び出されている場合、仮想マシンはこの2つの状況を「実行する必要はありません」と見なします.基本的に2回目のマークの時に回収されます.
このオブジェクトがfinalize()メソッドを実行する必要があると判定された場合、このオブジェクトはF-Queueというキューに配置され、後で仮想マシンによって自動的に確立された優先度の低いFinalizerスレッドによって実行されます.ここで実行とは、仮想機会がこのメソッドをトリガーすることを意味するが、実行が終了するのを待つことは約束されていない.これは、オブジェクトがfinalize()メソッドで実行が遅い場合、あるいはデッドサイクル(より極端な場合)が発生すると、F-Queueキュー内の他のオブジェクトが永久に待機し、メモリ回収システム全体がクラッシュする可能性が高い.finalize()メソッドは、オブジェクトが死の運命から逃れる最後の機会であり、後でGCはF-Queue内のオブジェクトに2回目の小規模なタグを付ける.オブジェクトがfinalize()で自分を救うことに成功する場合は、参照チェーン上の任意のオブジェクトに再関連付けるだけで、たとえばクラス変数やオブジェクトのメンバー変数に自分(thisキーワード)を割り当てると、2回目のタグで回収セットが削除され、オブジェクトが逃げていない場合は、基本的に本当に回収されます.
次にfinalize()を用いて自救し生存する例を示す
public class FinalizeEscapeGC {

	public static FinalizeEscapeGC SAVE_HOOK = null;
	public void isAlive() {
		System.out.println("yes,i am still alive:)");
	}
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println("finalize mehtod executed");
		FinalizeEscapeGC.SAVE_HOOK = this;
	}
	
	public static void main(String[] args) throws InterruptedException {
		SAVE_HOOK = new FinalizeEscapeGC();
		SAVE_HOOK.isAlive();
		SAVE_HOOK = null;
		System.gc();
		Thread.sleep(500);
		if(SAVE_HOOK!=null)
			SAVE_HOOK.isAlive();
		else {
			System.out.println("no, i am dead :(");
		}
		
		SAVE_HOOK = null;
		System.gc();
		Thread.sleep(500);
		if(SAVE_HOOK!=null)
			SAVE_HOOK.isAlive();
		else {
			System.out.println("no, i am dead :(");
		}
	}
	
}

実行結果:
yes,i am still alive:)
finalize mehtod executed
yes,i am still alive:)
no, i am dead :(

このプログラムにより,初めてSAVE_HOOK = null;でgc回収を行ったとき,GCはfinalize()メソッドを呼び出し,オブジェクトの再回収前に脱出に成功したことが分かる.2回目のSAVE_HOOK = null;でgc回収を行った場合、オブジェクトのfinalize()は既にGCに1回呼び出されているので、再度呼び出されることはなく、これでオブジェクトは回収される.
しかしfinalize()は使用を推奨しません.javaが誕生したときにC/C++プログラマーが受け入れやすいように妥協しただけで、実行コストが高く、不確定で、各オブジェクトの呼び出し順序を保証することはできません.