これは Firefox 3 の XPCOM に導入されたサイクルコレクタの簡単な概要です。既存の C++ クラスを XPCOM サイクルコレクションに加えるための変更を順を追って説明します。cyclical-ownership リークに困っているクラスがあれば、このページが役立つでしょう。
このドキュメントは Mozilla C++ 開発者向けです。
サイクルコレクタの役割
サイクルコレクタは、そのほとんどの時間を費やして、ガーベッジサイクルに加わる かもしれない、XPCOM オブジェクトへのポインタを蓄積して (または捨てて) います。これは、コレクタ作業の待機 (idle)ステージにおいて "疑わしい" refcount イベント (0 ではない N の N+1 から N) を通過する、とても素早い nsAutoRefCnt
レジスタおよび、そのアンレジスタの特殊な変異型です。
コレクタはバッファに居座る疑わしいポインタを定期的に検査します。これはコレクタ作業の調査 (scan)ステージです。このステージでは、コレクタは単独のサイクルコレクション・ヘルパークラスの各候補に繰り返し尋ね、ヘルパーが存在するときは、コレクタが候補 (自身) の子を記述するようにヘルパーに要求します。この方法で、コレクタが、疑わしいオブジェクトの手がかりとなる所有者の部分グラフを構築します。
すべての参照をもう一つのオブジェクトへ再び問い合わせ、参照カウントがすべてグループ内の内部ポインタで占められているオブジェクトを確立するオブジェクトのグループをコレクタが見つけた場合、開放の対象となる cyclical garbage グループに入れるかどうかが考慮されます。これはコレクタ作業のリンク解除 (unlink)ステージです。このステージでは、コレクタは見つかったガーベッジオブジェクトをざっと見渡し、それらのヘルパーオブジェクトに再び相談して、各オブジェクトをそれらの子から "リンク解除" するか尋ねます。
コレクタは JS ヒープを見渡す方法も知っており、所有者のサイクルをそこへ提出したり、そこから去らせたりできます。
コレクタの失敗例
サイクルコレクタは保守的なデバイスです。以下はガーベッジサイクルの収集に失敗する状況です。
- デフォルトで任意のポインタを疑うことはありません。オブジェクトは それら自身で疑う べきです。一般的に、
nsAutoRefCnt
よりもnsCycleCollectingAutoRefCnt
が使用されます。 nsICycleCollectionParticipant
に QI された (QI'ed)時は、ヘルパーオブジェクトを返すオブジェクトを横断するだけです。横断中に未知の境目 (edge)に遭遇した場合は、その境目で断念します。これはサイクルに加えられたすべての境目が関与していなければならないことを意味します。そうでなければ、サイクルは見つかりません。- ヘルパーオブジェクト上の
Traverse
メソッドとUnlink
メソッドは魔法ではありません。これらはプログラマが提供しており、正しく動作しなければなりませんが、正しく動作しないとコレクタが失敗します。 - コレクタはスタック上に存在する一時的な自身へのポインタを見つける方法を知りません。そのため、プログラムのトップループの近くからだけ実行することが重要です。追加の自身へのポインタがあってもクラッシュしませんが、自身のオブジェクト内で見つかる、参照カウントに加えることができないポインタ自身を見つけるため、サイクルの収集に失敗する可能性があります。
クラスをサイクルコレクタに加える方法
サイクルコレクタとあなたのクラスの間のインタフェースは、xpcom/base/nsCycleCollector.h
のコンテンツを使用して直接アクセスできます。しかし、あなたのクラスに注釈をつけるための、もっと簡単に使用できる便利なマクロが xpcom/base/nsCycleCollectionParticipant.h
にあります。一般的に、mBar
と mBaz
の 2 つの nsCOMPtr
エッジを持つ nsFoo
クラスを調整すると仮定すると、処理はいくつかの簡単な調整に絞られます:
nsFoo.h
とnsFoo.cpp
の両方にnsCycleCollectionParticipant.h
ヘッダを include します。nsFoo
の定義で、NS_DECL_ISUPPORTS
の行をNS_DECL_CYCLE_COLLECTING_ISUPPORTS
に変更します。nsFoo
の定義のパブリックな部分にNS_DECL_CYCLE_COLLECTION_CLASS(nsFoo)
の行を追加します。nsFoo.cpp
内のnsFoo
のインタフェースマップにNS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsFoo)
の行を追加します。NS_INTERFACE_TABLE_HEAD(nsFoo) NS_INTERFACE_TABLE2(nsFoo, nsIBar, nsIBaz) NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsFoo) NS_INTERFACE_MAP_END
nsFoo.cpp
内のNS_IMPL_ADDREF(nsFoo)
の行をNS_IMPL_CYCLE_COLLECTING_ADDREF(nsFoo)
に変更します。nsFoo.cpp
内のNS_IMPL_RELEASE(nsFoo)
の行をNS_IMPL_CYCLE_COLLECTING_RELEASE(nsFoo)
に変更します。nsFoo.cpp
にNS_IMPL_CYCLE_COLLECTION_CLASS_2(nsFoo, mBar, mBaz)
の行を追加します。
あなたのクラスを、この設計図よりもさらに複雑な構造にすることも可能です。例えば、あなたのクラスが複数の nsISupports
ベースクラスを持っており、破滅を明確にするいくつかの *_AMBIGUOUS
マクロを使用する必要がある場合です。または、あなたのクラスが NS_IMPL_CYCLE_COLLECTION_CLASS_N
マクロで効果が無い、複雑な所有者構造をしている場合です。後者の場合は、あなたのヘルパークラスに Traverse メソッドと Unlink メソッドを自分で実装する必要があるでしょう。これらの例では NS_IMPL_CYCLE_COLLECTION_TRAVERSE_{BEGIN,END}
マクロと NS_IMPL_CYCLE_COLLECTION_UNLINK_{BEGIN,END}
マクロの使用が役立ちます。これらの使用例は、より複雑な content/base/src/nsGenericElement.cpp
などのクラスを参照してください。あなたのクラスが切取られた部分であるか、他のクラスの集合である場合、切り取られたクラスや外部のクラスもサイクルコレクションに加えられることが重要です。そうしないと、サイクルコレクタにオブジェクトも収集させてしまいます。
Manually implementing the Traverse and Unlink methods
Each field that may contain cycle collected objects needs to be passed to the cycle collector, so it can detect cycles that pass through those fields.
The main macro for Traverse is NS_IMPL_CYCLE_COLLECTION_TRAVERSE:
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSomeMember)
Unlink works similarly:
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSomeMember)
These macros should handle a variety of cases, such as reference counted pointers to cycle collected nsISupports or non-nsISupports objects, as well as arrays of these pointers.