[ActionScript 3.0] クロージャが原因のメモリリーク
クロージャを利用した場合、それを内包する関数(メソッド)におけるローカル変数の状態のまとまりを、そのクロージャが実際に呼び出されたときのために、スクリプトエンジン(Flash PlayerやAIR)が保持しつづける。
そのため、ローカル変数のオブジェクトは関数終了後も破棄されず、クロージャを保持しているオブジェクトが破棄されないかぎり、ずっとメモリを使用したままになる。
[as3]
public class TestClosureMem extends Sprite {
private var sampleTimer:Timer = new Timer(50, 20);
private var checkTimer:Timer = new Timer(200);
private var fnVec:Vector.
public function TestClosureMem() {
sampleTimer.addEventListener(TimerEvent.TIMER, doSample);
sampleTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onComplete);
checkTimer.addEventListener(TimerEvent.TIMER, checkMemory);
sampleTimer.start();
checkTimer.start();
}
private function checkMemory(e:TimerEvent):void {
trace(System.totalMemory);
}
private function doSample(e:TimerEvent):void {
var bmpData:BitmapData = new BitmapData(1000, 1000);
fnVec.push(
function ():void {
// bmpDataをここで使わなくても、そのデータは保持されつづける
}
);
}
private function onComplete(e:TimerEvent):void {
// ここでfnVecを解放しないと大変なことになる。
// OSが不安定になることもあるので、テストのときは要注意。
fnVec = null;
trace(“— closure cleared —“)
fnVec = new Vector.
sampleTimer.start();
}
}
[/as3]
この例の場合、クロージャ(無名関数)をfnVecというVectorインスタンスが保持しているから、それが破棄されないかぎりメモリは増えつづけていく。
stageからの参照のつながり(スコープチェーン)は、以下のようになっている。
stage - TestClosureMem - fnVec - 各クロージャ
よって、TestClosureMemインスタンスかfnVecがガベージコレクション(GC)の対象にならないかぎり、bmpDataは破棄されずにどんどんメモリを占有していく。
GCの仕組みがある言語でも、メモリの問題からは逃れられない。そこにクロージャがからんでくるとさらに厄介なことになるので、特段の理由がないならばあまりうかつにクロージャを使わないほうがいいのかもしれない。