ガベージコレクターはどのようにスタックオーバーフローを回避しますか?


23

それで、ガベージコレクターがどのように機能するかを考えていて、興味深い問題を考えました。おそらく、ガベージコレクターはすべての構造を同じ方法で走査する必要があります。彼らは、リンクリストやバランスの取れたツリーなどを横断している天気を知ることができません。また、検索でメモリを使い果たすこともできません。1つの可能な方法、およびALL構造をトラバースする唯一の方法は、バイナリツリーの場合と同じようにすべて再帰的にトラバースすることです。ただし、これにより、リンクリストまたは単にバランスの悪いバイナリツリーでスタックオーバーフローが発生します。しかし、私が今まで使用したガベージコレクション言語はすべて、このようなケースに対処するのに問題がないようです。

ドラゴンブックでは、「スキャンされていない」種類のキューを使用しています。基本的に、構造を再帰的にトラバースするのではなく、キューとしてマークする必要があるものを追加し、最後にマークされていないものごとに削除します。しかし、このキューは非常に大きくなりませんか?

それでは、ガベージコレクタはどのように任意の構造をトラバースしますか?このトラバーサル技術はどのようにオーバーフローを回避しますか?


1
GCは、ほぼ同じ方法ですべての構造を走査しますが、非常に抽象的な意味でのみです(回答を参照)。彼らが具体的に物事を追跡する方法は、教科書で見つけることができる基本的なプレゼンテーションで示されるよりもはるかに洗練されています。そして、それらは再帰を使用しません。さらに、仮想メモリでは、実装が不適切な場合、GCのスローダウンとして表示され、メモリオーバーフローとしてはめったに表示されません。
babou

トレースに必要なスペースが心配です。しかし、トレースされて使用中のメモリと、潜在的に回収可能なメモリを区別するために必要なスペースや構造についてはどうでしょうか。これにはかなりのメモリコストがかかる場合があり、おそらくヒープサイズに比例します。
babou

16バイト以上のオブジェクトサイズのビットベクトルでそれが行われると思ったので、オーバーヘッドは少なくとも1000倍少なくなります。
ジェイク

それを行うには多くの方法があり(回答​​を参照)、それらはあなたの質問に答えるトレースにも使用できます(提案するスタックまたはキューではなく、ビットベクトルまたはビットマップをトレースに使用できます)。多くのオブジェクトが存在する可能性がある小さなオブジェクトのスペースを無駄にしない限り、すべてのオブジェクトが大きいと想定することはできません。スペースを心配する必要はありません。仮想メモリを使用している場合、スペースはほとんど問題にならず、問題は大きく異なります。
babou

回答:


13

私はガベージコレクションの専門家ではないことに注意してください。この回答では、テクニックの例を示しています。ガベージコレクション手法の代表的な概要であるとは主張しません。

スキャンされていないキューが一般的な選択肢です。キューは大きくなる可能性があります-最も深いデータ構造と同じくらい大きくなる可能性があります。キューは通常、ガベージコレクションスレッドのスタックではなく、明示的に保存されます。

ノードの子の1つを除いてすべてがスキャンされると、ノードはスキャンされていないキューから削除できます。これは基本的に末尾呼び出しの最適化です。ガベージコレクターには、ノードの最も深い子を最後にスキャンしようとするヒューリスティックを含めることができます。たとえば、LispのGC carは、のcons前にをスキャンする必要がありcdrます。

スキャンされていないキューを保持しないようにする1つの方法は、ポインターを所定の位置に変更して、子が一時的に親を指すようにすることです。これは、ガベージコレクター以外のコンテキストで使用される定数メモリツリートラバーサル手法です。この手法の欠点は、GCがデータ構造を走査している間、データ構造が無効になるため、GCが世界を停止する必要があることです。これは取り決めのブレイクではありません。多くのガベージコレクターは、結果としてガベージを見逃すことはできても、見逃すことができるフェーズに加えて、世界を停止するフェーズを含んでいます。


2
最後のパラグラフで説明されたテクニックはしばしば " ポインター逆転 " と呼ばれます
さまようロジック

@WanderingLogicはい、ポインターの反転は、私自身の答えでそれをどのように呼んだかです。Deutsch、SchorrおよびWaite(1967)によるものです。しかし、一定のメモリで動作している状態に誤りがあります:それは必要ないと各セルの余分なビットのpこれはビットスタックを用いることによって低減することができるものの、ポインタを。あなたが参照する受け入れられた答えは、同じ理由でまったく正確でも完全でもありません。log2pp
-babou

私がしているこれらの余分なビットを必要とすることなく、カスタムGCでポインタの逆転を使用します。トリックは、メモリ内のオブジェクトの特別なメモリ内表現を使用することでした。つまり、オブジェクト「ヘッダー」は中央にあり、ヘッダーフィールドの前にポインターフィールドがあり、その後に非ポインターフィールドがありました。さらに、すべてのポインターが整列され、ヘッダーには最下位ビットが常に設定されたフィールドが含まれていました。したがって、ポインタの反転バックトラック中に、次のポインタに到達し、オブジェクトで終了したことに気付くことが、余分なビットなしで明確に行われます。このレイアウトは、OOP継承もサポートしていました。
トーマスポーリン

@ThomasPorninビット情報はどこかにある必要があると思います。問題はどこですか?これをチャットで議論できますか?私は今、去らなければなりませんが、私はこれの一番下に行きたいです。または、ウェブ上で到達可能な説明はありますか?
babou


11

一言で言えばガベージコレクタは、再帰を使用しないでください。これらは、本質的に2つのセット(組み合わせ可能)を追跡することにより、トレースを制御するだけです。トレースとセル処理の順序は関係ないため、実装をかなり自由に表現できます。したがって、実際にはメモリ使用量が非常に少ない多くのソリューションがあります。これは、ヒープがメモリ不足になると正確に呼び出されるため、不可欠です。新しいページを簡単に割り当てることができるため、大きな仮想メモリでは状況が少し異なります。そして、その大敵はスペースの不足ではなく、データの局所性の欠如 です

質問が当てはまらない参照カウントではなく、ガベージコレクターのトレースを検討していると思います。

UV

最初に注意することは、すべてのトレースGCは、プログラムからアクセス可能なメモリ内のセルの有向グラフの体系的な調査に基づいた同じ抽象モデルに従うことです。メモリセルは頂点であり、ポインタは有向エッジです。そのために次のセットを使用します。

  • VVV=UT

  • U

  • T

  • H

VUUT

UV

UcVUcUT

UUV=TVHVV

VうんうんT

また、セルとは何か、1つまたは複数のサイズであるかどうか、それらの中でポインターを見つける方法、それらを圧縮する方法、およびガベージコレクションに関する書籍や調査で見つけることができる他の多くの技術的な問題に関する詳細はスキップします。

うん

既知の実装が異なるのは、これらのセットが実際に表現される方法です。多くの手法が実際に使用されています:

  • ビットマップ:各メモリセルに1ビットのマップ用に一部のメモリ空間が保持されます。これは、セルのアドレスを使用して見つけることができます。ビットは、対応するセルがマップで定義されたセットにあるときにオンになります。ビットマップのみを使用する場合、セルごとに2ビットのみが必要です。

  • または、各セルにマークするための特別なタグビット(または2)のスペースがあります。

  • log2pp

  • セルのコンテンツとそのポインターで述語をテストできます。

  • 表示されているセットに属するすべてのセルのみを対象としたメモリの空き部分にセルを再配置できます。

  • VTTU

  • 単一のセットであっても、これらの手法を実際に組み合わせることができます。

前述のように、上記のすべては、実装されているガベージコレクターによって使用されていますが、奇妙に思えるかもしれません。それはすべて、実装のさまざまな制約に依存します。また、メモリ使用量がかなり安くなる可能性があります。最終結果には関係ないため、その目的のために自由に選択できる処理順序ポリシーが役立つ可能性があります。

新しい領域にセルを転送する、最も奇妙なものと思われるものは、実際には非常に一般的です。それはコピーコレクションと呼ばれます。ほとんどの場合、仮想メモリで使用されます。

明らかに再帰はなく、ミューテーターアルゴリズムスタックを使用する必要はありません。

もう1つの重要な点は、多くの最新のGCが大きな仮想メモリ用に実装されていることです。その後、新しいページを簡単に割り当てることができるため、実装するスペースと余分なリストまたはスタックを確保することは問題になりません。しかし、大きな仮想記憶では、敵はスペースの不足ではなく、場所の不足です。次に、セットを表す構造とその使用は、データ構造とGC実行の局所性を維持するように調整する必要があります。問題はスペースではなく時間です。不適切な実装は、メモリオーバーフローよりも許容できない速度低下を示す可能性が高くなります。

これらの手法のさまざまな組み合わせに起因する多くの特定のアルゴリズムについては言及しませんでした。


4

スタックオーバーフローを回避する標準的な方法は、明示的なスタックを使用することです(ヒープにデータ構造として格納されます)。それはこれらの目的にも有効です。ガベージコレクターは、多くの場合、調査/トラバースする必要があるアイテムのワークリストを持ち、これがこの役割を果たします。たとえば、「未スキャン」キューは、まさにこの種のパターンの例です。キューは大きくなる可能性がありますが、スタックセグメントに格納されないため、スタックオーバーフローは発生しません。いずれにしても、ヒープ内のライブオブジェクトの数よりも大きくなることはありません。


GCが呼び出されると、通常はヒープがいっぱいになります。もう1つのポイントは、スタックとヒープが同じメモリ空間の両端から大きくなることです
。– babou

4

ガベージコレクションの「古典的な」説明(例:マークウィルソン、「ユニプロセッサガベージコレクションテクニック」、メモリ管理に関する国際ワークショップ、1992、(代替リンク)、またはAndrew AppelのModern Compiler Implementation(Cambridge University Press、 1998))、コレクターは「Mark and Sweep」または「Copying」に分類されます。

マークコレクターとスイープコレクターは、@ Gillesの回答で説明されているように、ポインター反転を使用して余分なスペースを必要としません。Appelは、Knuthがポインター反転アルゴリズムをPeter Deutsch、およびHerbert SchorrとWM Waiteに帰していると言います。

コピーガベージコレクターは、Cheyneyのアルゴリズムと呼ばれるものを使用して、余分なスペースを必要とせずにキュートラバーサルを実行します。このアルゴリズムは、CJ Cheyney、「非再帰的リスト圧縮アルゴリズム」、Commで導入されましたACM、13(11):677-678、1970。

コピーガベージコレクタには、from-spaceと呼ばれる収集しようとしているメモリのチャンクと、to-spaceと呼ばれるコピーに使用しているメモリのチャンクがあります。scanコピー先スペースは、コピーされたがスキャンされていない最も古いレコードをfree指すポインターと、コピー先スペースの次の空き位置を指すポインターを持つキューとして編成されます。ウィルソンの論文からのこの写真は次のとおりです。

Cheyneyのアルゴリズムの例

to-spaceの各項目をスキャンすると、from-spaceの子freeをto-spaceのポインターにコピーしてから、from-spaceの子へのポインターをto-spaceの子の新しいコピーに変更します。データ構造がツリーではない場合(子が複数の親を持つことができる場合)に使用する必要がある余分なトリックがあります。その場合、子をfrom-spaceからto-spaceにコピーするとき、古いバージョンの子を、子の新しいコピーへの転送ポインターで上書きする必要があります。その後、古いバージョンの子への別のポインターをスキャンすると、すでにコピーされていることに気付き、再度コピーしないでください。


実際、私の答えで説明したように、Mark + SweepとCopyコレクションは同じ抽象グラフアルゴリズムです。MSとコピーコレクションは、抽象アルゴリズムで使用されるセットの実装方法のみが異なり、両方のファミリが含まれており、多くのバリエーションがあり、回答で説明したセット実装テクニックのいくつかの組み合わせが含まれます。一部のGCバリアントは、実際には同じGCでMSとコピーを混合します。MSとコピーを分離することは、本を構成する便利な方法と見られていますが、それはarbitrary意的であり、時代遅れのビジョンだと思います。
-babou

@babou:訪問するすべてのものをコピーするコピーアルゴリズムを使用している場合(低速ですが、ワーキングセットがそれほど大きくない小さなプラットフォームでは有用かもしれません)、いくつかのアルゴリズムは以前はスクラッチパッドとして再配置されたオブジェクトによって占められていたメモリ。また、各読み取りの前後にオブジェクトの有効性をチェックし、オブジェクトが移動した場合は転送ポインターを追跡するという条件で、コレクション中に他のスレッドにオブジェクトへの読み取り専用アクセスを実行する制限された機能を取得できます。
supercat

@supercatあなたが何を言おうとしているのか、あなたの意図は何なのかわかりません。あなたの声明のいくつかは正しいようです。しかし、GCサイクルが終了する前にfrom-spaceを使用する方法を理解していません(転送ポインターが含まれています)。そして、それは何のためのスクラッチパッドでしょうか?アルゴリズムをどのように簡素化しますか?GCの実行中に実行される複数のミューテータースレッドに関しては、これは主に直交的な問題ですが、実装に深刻な影響を与える可能性があります。私はコメントでそれを取り上げようとはしないでしょう。読み取り専用アクセスで問題が発生する可能性は低くなりますが、悪魔は詳細にあります。
-babou
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.