[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. = new 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の仕組みがある言語でも、メモリの問題からは逃れられない。そこにクロージャがからんでくるとさらに厄介なことになるので、特段の理由がないならばあまりうかつにクロージャを使わないほうがいいのかもしれない。

ActionScript 3.0

Posted by takasho