GC判定再び
GCされたかテストするクラスの改良版。
最後の参照を消すとき、
_instanceVar = null; //これでいつか解放されるはずだが・・
と書いているところを、
//常に戻り値のnullが代入されるが、同時に漏れを確認 _instanceVar = GC.shouldBeGarbage(_instanceVar); // 以降、ゴミとして回収されるべき!
と書くことにします[*注]。これで参照の消し忘れがあれば、直後にエラーになってくれます。多分。
ちなみにFlexでは子SWFがアンロードされると、以下のように表示されるのでこれの出番は少ないかもしれません。
[SWF のアンロード]Users:masuda:Documents:Flex Builder 3:Test:bin-debug:child.swf
以下にいくつかサンプルを挙げます。
ドキュメントクラスのコンストラクタに書かれているコードだと思って下さい。
ローカル変数はGCされる:
var o:DisplayObject = new Sprite(); GC.shouldBeGarbage(o); //=> GC succeeded: 4489216 => 4771840
インスタンス変数に退避したため延命され、GCされない:
var o:DisplayObject = new Sprite(); _instanceVar = o; GC.shouldBeGarbage(o); //=> Error
表示リストに残っているため、GCされない:
_instanceVar = new Sprite(); addChild(_instanceVar); //removeChild(_instanceVar); //この行が必要だった _instanceVar = GC.shouldBeGarbage(_instanceVar); //=> Error
ローカル変数はクロージャに延命され、クロージャはstageに延命されるのでGCされない:
var o:DisplayObject = new Sprite();
addChild(o);
removeChild(o);
GC.shouldBeGarbage(o); //=> Error
//o = null; //この行が必要だった
stage.addEventListener(
Event.ENTER_FRAME,
function(evt:Event):void {/*どの変数も参照してなくても害*/}
);
クロージャとメモリリークについての親切な解説はこちら:
http://www.imajuk.com/blog/archives/2008/04/post_3.html
以下、ソースコードです:
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.net.LocalConnection;
import flash.system.System;
import flash.utils.Dictionary;
public class GC {
private static var sprite:Sprite = new Sprite();
public static function start():void {
try {
new LocalConnection().connect('foo');
new LocalConnection().connect('foo');
} catch (e:*) {}
}
public static function shouldBeGarbage(value:Object):* {
try {
//テストは1フレ後だが、この時点のスタックトレースがほしい
throw new Error('Probably, it is not garbage:' + value);
} catch (e:Error) {
var dict:Dictionary = new Dictionary(true);
dict[value] = true;
value = null;
var before:uint = System.totalMemory;
GC.start();
var err:Error = e;
sprite.addEventListener(Event.ENTER_FRAME, function(evt:Event):void {
evt.target.removeEventListener(evt.type, arguments.callee);
for (var key:Object in dict) throw err;
trace('GC succeeded:', before, '=>', System.totalMemory);
});
}
return null;
}
}
}
[*注] 以下の例は、全ての参照を消すのに先だってshouldBeGarbageを行うためGCが成功しそうにありませんが、実際のところ、このように書いてもテストをパスします。
GC.shouldBeGarbage(_instanceVar); _instanceVar = null;
恐らく、LocalConnectionによる強制GCの発動は即座に行われずに、一度Flash Playerに制御を戻した後行われているのだと想像しています・・。そうなると、shouldBeGarbage(x)の意味合いとしては、「現在実行中のコードを抜けた後には、xはゴミとなり回収されるべきだ」といったところでしょうか。
