FlashPlayer9のガベージコレクタのメモリ解放の考察

2008.03.29 / actionscript

Tweenerのメモリ解放の挙動について調べている中で、そもそもFlashPlayer9(AVM2)のガベージコレクタの挙動が気になったので、簡単な検証コードを書いてみました。結構興味深い挙動をしていたことに気づいたのでメモ。ちなみにPlayerはWindowsデバッグ用プレイヤーを利用しています。これはガベージコレクタを強制的に呼び出すSystem.gc()がデバッグ用プレイヤーしか利用できないためです。

検証

検証用コードはこんなかんじ。Spriteオブジェクト作ってaddChildして、removeしてnullつっこんで、それぞれの過程でメモリ使用量を計測してるだけ。

  package {
      import flash.display.Sprite;
      import flash.display.DisplayObject;
      import flash.events.Event;
      import flash.system.System;
  
      public class GCTest extends Sprite{
          
          public function GCTest() : void {
              log("[0]" + System.totalMemory);
  
              var sprite : Sprite = new Sprite(); 
              log("[1]" + System.totalMemory);
              
              addChild(sprite);
              log("[2]" + System.totalMemory);
              
              removeChild(sprite);
              log("[3]" + System.totalMemory);
              
              System.gc(); // A
              log("[4]" + System.totalMemory);
              
              sprite = null;
              log("[5]" + System.totalMemory);
              
              System.gc(); // B
              log("[6]" + System.totalMemory);                
          }
      }
  }
  

さて、単純に考えるとオブジェクト作ったものを完全に解放しきっているので[0]の値と[6]の使用量は同じ値になるはず。でも僕の環境での結果はこんな感じでした。

  [0]2240512
  [1]2330624
  [2]2334720
  [3]2338816
  [4]2334720
  [5]2347008
  [6]2334720
  

オブジェクトの作成、addChildまでは分かりやすく使用量は上がっていますが、

  • GC実行直後はメモリ使用量が減らずに増える
  • nullを代入してオブジェクトをGC回収対象にしても完全にメモリ解放されていない([0 ]!=[5 or 6])

なんてことがわかります。とりあえずGC強制呼び出しは悪影響っぽいのでGC呼び出しをやめて、上の例のA, Bの箇所をコメントアウトするとこんな結果になりました。

  [0]2240512
  [1]2330624
  [2]2334720
  [3]2338816
  [4]2338816
  [5]2342912
  [6]2342912
  

null入れたらメモリ使用量上がってます。なにこれw

見直し

そこでremoveChildの定義を見直し。するとこんな風に書かれています。

DisplayObjectContainer インスタンスの子リストから指定の child DisplayObject インスタンスを削除します。削除された子の parent プロパティは null に設定されます。その子に対する参照が存在しない場合、そのオブジェクトはガベージコレクションによって収集されます。DisplayObjectContainer の子より上位にある表示オブジェクトのインデックス位置は 1 つ下がります。 DisplayObjectContainer

なるほどー。removeChildすると、もうそれだけでGCの対象になるわけですね。これremoveChildだけじゃだめで、null参照させないとダメだと勘違いしてました。あと、Twitter経由でこんなこと教えていただきました。

@ryo_katsuma add 分と new 分というニュアンスがちとわからないのだけど、 GC はゴミが残るので正確な差分は求められないかもしれません
via Twitter

@ryo_katsuma オブジェクト単位じゃなくてブロック単位で開放するらしいので。と、 kamijo さんのブログに書いてありました。
via Twitter

kamijoさんのblogの該当記事がまだ見つけられていないのですが、どうもGCは逐一オブジェクト単位でメモリ解放を実行しているのではない、ということのようですね。また、null参照させたりして、解放の対象にさせても完全にクリーンアップしてくれることを保証してくれるわけでは無さそうです。

そんなわけで

GCの挙動を追っかけるためには、仕様をもう少し読みこなさないとダメぽいです。あとJavaのGCについてももう少し調べたいと思いました。