マーク・アンド・スイープ

マーク・アンド・スイープmark-and-sweep)は、ガベージコレクションの実装方法およびガベージコレクタの動作方法の一つ。

方法 編集

基本的な方針は、あるオブジェクト(ここでは、ルートオブジェクトと呼ぶ)からのトラバース(オブジェクトから別のオブジェクトへの参照を辿ること)によって到達可能なオブジェクトに印(マーク)をつけ、印のつかなかったオブジェクトを破棄(スイープ)する、というものである。

具体的な手順の一例は次のようになる:

  1. ルートオブジェクトに印をつける
  2. 直前に印をつけたオブジェクトから、1回のトラバースで到達可能なすべてのオブジェクトに印をつける(すでに印がついているものについては何もしない)
  3. 2の操作を、印がつかなくなるまで行う
  4. 印がついてないオブジェクトを破棄する

特徴 編集

マーク&スイープ方式は、参照カウントにおける循環参照問題を回避し、不要なオブジェクトを確実に破棄できる。また、参照カウントを使わない分、ガベージコレクタが動作していない間の処理は高速である。

反面、ガベージコレクション自体は、参照カウント方式より処理時間がかかるため、通例次のような適当なタイミングを見計らって時々行う。

  • メモリが不足してきたとき
  • システムが何もしていないとき
  • プログラムから明示的な指令があったとき(JavaSystem.gc()メソッドや.NET FrameworkSystem.GC.Collect()メソッドなど)

参照カウントによる寿命管理をメインに、マーク&スイープなどを補助的に併用するシステムや、世代別ガベージコレクションのようにコピーGCとマーク&スイープを組み合わせる方式もある。Pythonは参照カウントをメインにして、伝統的なマーク&スイープとは逆順の探索アルゴリズムによる世代別GCも補助的に併用している[1]

マーク&スイープは、GCルートからの参照の到達可能性を追跡するため、オブジェクトの数が増えるほどGCの処理時間が増加していく。また、GC実行中はアプリケーション全体の動作(GC以外のすべてのスレッド)をいったん停止する必要がある(ストップ・ザ・ワールド)。この停止時間の問題を改善するため、世代別GCと組み合わせたうえで、アプリケーションの停止時間を最小化して並行動作するコンカレント・マーク・スイープ (Concurrent Mark Sweep, CMS) と呼ばれる方式も考案されている[2]。ただしCMSにも問題点はあり、JavaではCMSの代わりにガベージファースト (G1) と呼ばれる発展方式が推奨されている[3]

保守的なガベージコレクタ 編集

C言語C++など、ガベージコレクタを仕様に含んでいないプログラミング言語でマーク・アンド・スイープを実行するには、マシンスタックやマシンレジスタ内にも参照がないかを確認する必要がある。しかし、通常マシンスタックやマシンレジスタの値が参照アドレスを表しているのか、ただの数値を表しているのかを区別することはできない。そこで、マシンスタックやレジスタ中の値は全て参照アドレス値であると解釈して、該当アドレスのオブジェクト回収を保留する。このような実装を保守的なガベージコレクタと呼ぶ。処理手順は以下のようになる。

  1. まず、使用中であることが確実である参照を調べる。具体的には、スタック領域や定数領域にあるポインタ変数などである。見つかった参照から到達可能なオブジェクトに印をつける。
  2. そして、このオブジェクトからの参照を順々にたどっていき、使用中のメモリやオブジェクトの一覧を作る。
  3. この時、スタック上やオブジェクト内にある参照でないデータも、参照をあらわすデータと見なして処理を進める[注釈 1]。使用中のメモリを誤って解放してしまうことの方が、解放しないことよりも圧倒的に問題なので、使用中かどうか疑わしいメモリは解放しないのが安全である。それゆえ、保守的と呼ばれる。
  4. そうして、使用中でないことが確実なメモリの一覧を作り、それを解放する。

C++11Boost C++ライブラリshared_ptrなど参照カウント方式のメモリ管理とは異なり、保守的なガベージコレクタでは、特定のライブラリやネイティブスレッドとの同時使用によりトラブルが起こることがあるので、注意が必要である。

実装例 編集

脚注 編集

注釈 編集

  1. ^ 例えば、C/C++ではオブジェクトへの参照を示すポインタを他の型に変換(オブジェクトのメモリアドレス値をintptr_t等のポインタ互換整数型に代入するなど)した状態で保持し、また戻して使用することが可能である。

出典 編集

関連項目 編集