The Azul Garbage Collector

原文

翻訳: 中村 成洋

Introduction

Java言語の仕様はGCによって未使用領域を再利用することが規定されており、手動のメモリデアロケーションを禁止する。 GCは必要とされないオブジェクトを解放するという心配事からプログラマを解放する。 また、GCはいくらかの一般的なバグの発生を防ぐ、たとえばメモリリーク、ダングリング・ポインタのバグ、二重freeバグである。

GCは明瞭なアドバンテージを持つが、いくらかの問題も持っている。 もっとも重要な問題は、GCの実用的な実装(商用Javaランタイム)が大抵予測不能な「ポーズ」をコレクション時に伴うのである。 Javaプログラムのサイズと複雑さが拡大するにつれ、GCのポーズは、Javaソフトウェアアーキテクトのますます重要な問題となっている。

これを回避するためにエンタープライズJavaで広く使用されている技術は、プログラムを分散させることだ。 これはヒープサイズを小さく保つこと、ポーズを短くすることが可能で、また、他のプログラムがポーズしている間もいくらかのリクエストは継続できる。 とはいえ、これはGCが扱う仕事量が、それらが実行されるハードウェアの機能をフルに活用することができないことを意味する。 我々はGil Tene(AzulSystemsのCTO,共同創立者)とこの件について話した。彼は、GCはハードウェアの進化についていけてないことを示唆した。 10年前、512MB-1GBのヒープサイズについて多く検討されており、高性能のコモディティサーバは1GB-2GB RAMと2コアCPUが積まれていた。近代的なコモディティハードウェアのサーバーは、通常24から48の仮想または物理的なコアを持つシステム上で実行されており、96ギガバイト、256GBのメモリを持っている。 時間の経過とともに、コモディティハードウェアのメモリ容量が100倍に増加している。そのため、一般的に使用されるGCのヒープサイズは倍増している。 Teneは、GCはハードウェアと多くの大規模なエンタープライズアプリケーションのソフトウェアの両方の要求に対し大幅に遅れていると主張している。

Collector Mechanisms

共通のタスク

Javaランタイムのガベージコレクタの実装はさまざまだが、商業目的のJVMとGCのモードで実行される一般的な、不可避のタスクがある。 これらの一般的なタスクが含まれている:

GCの特定のアルゴリズムに応じて、これらのタスクは別々のフェーズで実行したり、1フェーズの一部として実行可能。 たとえば、一般的に使われる Tracing Collectors (商用JVMの旧世代のGCで普及) はしばしば別の Mark, Sweep, Compactionフェーズで識別、再利用、再配置を行う。 一方、一般的に使われる Copying Collectors (商用JVMの新世代のGCで普及)は、通常、コピーの単一パス内で3つのすべてのタスクが実行される(すべての生存オブジェクトが新しい場所にコピーされる)。

並列性と並行性

GCは、シングルスレッドまたは並列のどちらでもよい:

それらは、Stop-the-world (STW) か並行のどちらかになる:

応答性(Responsiveness)と敏感性(Sensitivity)

伝統的な"Stop-the-world" コレクタ実装は明らかにアプリケーションの応答性に影響を与える。 一般的ではないが理解されているものは、"すこしの並行"と"ほぼ並行"コレクタは処理の一部の「ポーズ」によって、応答性の問題が明らかになるということ。 アプリケーションの応答性とスケーラビリティの制限は、一般的にstop-the-worldなポーズを起こすような処理や、ポーズを引き起こすような操作に影響を受ける。

アプリケーションが影響を受ける事柄の測定基準例:

  1. Live set size: ヒープ内の生存データ量によってコレクタの特定のサイクルで行うべき仕事量を決定するのが一般的。コレクタが"Stop-the-world"ポーズの間にヒープにおける生存データ量に依存する操作を実行するとき、アプリケーションの反応性は生存データ量に影響を受ける。生存データ量に依存する傾向のある操作はマーキング(Mark-Sweep,Mark-Sweep-Compact,もしくはMark-Comapctコレクタの操作)、コピー(Copyingコレクタの操作)、そしてヒープコンパクションの間の参照の再配置(コンパクションを行うコレクタの操作)である。
  2. Heap size: 使用されるアルゴリズムとメカニズムによっては、ヒープの総サイズはコレクタの特定のサイクルで行うべき仕事量を決定するかもしれない。コレクタがヒープのメモリ量(生存・死亡は関係なく)に依存した操作を実行し、それらの操作がstop-the-worldなイベント中に実行されるとき、アプリケーションの応答性はヒープサイズに影響を受ける。ヒープサイズに依存する傾向がある操作はスイープする(Mark-SweepとMark-Sweep-Compactの操作)コレクタ。
  3. Fragmentation and compaction: フラグメンテーションは避けられない。そして、その結果がコンパクションなのだ。あなたがオブジェクトを割り当て、そしていくらかが死んだとき、ヒープは穴を作る。それはいくらかのオブジェクトには十分な大きさであるが、それ不十分な場合もある。時が経つにつれて、穴は小さくなる。最終的に、あなたはヒープ内のスペースのあるポイントを得たとしても、その利用可能な穴よりも大きいいくらかのオブジェクトの場所はない。そのため、コンパクションはアプリケーションを作動させ続けるのに必要となる。コンパクションはアロケーションを進めるための連続した領域を解放するために、ヒープ内のいくつかの生存オブジェクトを再配置する。生存オブジェクトがコンパクション中に新しい場所に移動されている場合、再配置されたオブジェクトへの参照はすべて記録し、新しい場所へ再度マッピングする必要がある。すべての商業JVMはヒープをコンパクト化する処理が含まれている(ヒープが時間と共に使い物にならなくなるのを防ぐため)。JRockit,Hotspot,J9のJVMのすべてのコレクタのモード(並列、もしくはそれ以外)はstop-the-worldの間のみコンパクションを行う。コンパクションのポーズは一般的にアプリケーションが通常の動作の中で経験する最長のポーズだ。そのため、これらのコレクタはヒープのフラグメンテーションとサイズに影響されやすい。
  4. Mutation rate: プログラムがメモリ内の参照を更新する速度がどの程度かという定義は、ヒープの速さ、具体的にはヒープ内のオブジェクト内のポインタの変更速度となる。ミューテーションレートは一般的にアプリケーションの負荷に比例する。つまり、あなたが1秒以内に行う操作が多いほど、ヒープ内の参照に対する変更も多くなる。したがって、コレクタがポーズの間だけ変更された参照をマークできる場合、または、並行コレクタがスキャニングフェーズが完了する前の状態では、変更された参照を何度も訪れなければならない場合、コレクタは負荷に対して影響され、高いミューテーションレートは大きなアプリケーションポーズへと繋がる。
  5. Number of weak or soft references: HotspotのCMSやParallelGCなどのよく知られるGCを含む、いくらかのコレクタはstop-the-worldポーズにおいてのみweakもしくはsoft referenceを処理する。このように、ポーズ時間はweak,soft referenceの数に影響される。
  6. Object lifetime: ほとんどの割り当てられたオブジェクトは若いうちに死ぬので、コレクタはよく新世代と旧世代を区別する。世代別コレクタは長寿命のオブジェクトとは別に、最近割り当てられた若いオブジェクトをコレクションする。多くのコレクタが新・旧世代を扱う際は異なるアルゴリズムを使用する - 例えば、これらのコレクタはシングルパスで新世代全体をコンパクションすることができるが、旧世代を伴う場合は長い時間を要するかもしれない。しかしながら、多くのアプリケーションは長い間生きたデータセットを持っており、そのデータセットの大半は静的なものではない(例えば、キャッシュは企業システムの非常に一般的なパターン)。世代の仮定は効果的なフィルタだが、旧世代のオブジェクトを処理するのにより手間がかかるとすれば、あなたのコレクタはオブジェクトの寿命に影響を受けるようになる。

応答時間が重要だと考えるなら、並行コレクタがアプリケーション動作の測定基準においてそれ程影響がないことを示す必要性が明確に存在する。 しかしながら、製品システムにおいて広く使われているコレクタたちはしばしばこれらの目標を達成できない。

Teneによれば、Zing platformに含まれる Azul GPGCコレクタはこれら多くの測定基準において影響を受けづらく、広い操作範囲において堅牢であり続けるように設計されているそうだ。 保証されたシングルパスマーカを使うことで、コレクタはミューテータレートに完全に影響を受けない。 並行でヒープ全体をコンパクションするため、コレクタはフラグメンテーションの影響をうけない。 並行でweak,soft,final referenceを処理するので、コレクタはそのような機能を使うアプリケーションに対する影響を受けない。 クイックリリースの使用と、load valueバリアのself-healingの特性によって、コレクタは「急いた状態」で操作のフェーズを完了させること(性能が落ちるかもしれない、フェーズが完了しないかもしれないという恐怖によるもの)、または、stop-the-worldに落ちずに進めることによる影響を避けることができます。 以下のセクションで、私たちはこれがどのように達成されるかを、他の一般的に使用されたコレクタアルゴリズムとAzulのアプローチを比較していく。

The Azul Garbage Collector

HotspotベースのJVMに含まれる、AzulのGPGC コレクタ(Generational Pauseless Garbage Collectorの略称)は並列であり、並行でもある。 GPGCは多くの製品システムで何年も使われており、伝統的なその他の並行コレクタが停止する原因に対して影響をかなり受けなくなった、もしくはまったく受けないでいる。 これは、Azul GPGCコレクタが特にフラグメンテーション、アロケートレート、ミューテーションレート、soft,weak reference を使った場合、そしてヒープトポロジーに影響を受けないよう、特別に設計されたことが要因だ。 TeneはGPGCは世代別GCでありながら、これは大部分が効率尺度であると説明した; GPGCは新・旧世代と同じGCメカニズムを使い、これらは両方が並行であり、さらにコンパクションされる。 最も重要なことは、GPGCはstop-the-worldに落ちることがないということ。 すべてのコンパクションはアプリケーションと同時に実行される。

Teneによると、アルゴリズム設計の中心的なテーマはどんなフェーズも終えるために「急ぐ」が必要ない、というアイデアだそうだ。 早くフェーズを終えて解放される必要のあるミューテータに、いかなるフェーズも負荷をかけることはない。 コレクションを再び開始する前にいくつかのフェーズを完了するための"競争"はない。 – 再配置は絶え間なく動き、そしていかなるポイントでもメモリを解放できる。 そのうえ、すべてのフェーズが並列であるので、単により多くのGCスレッドを加えることによって、GCはいろいろなミューテータスレッドについて行くことができる。

The Loaded Value Barrier

GPGCの設計のコアとなる部分は、Teneが"loaded value barrier"と呼ぶもの(メモリからロードされるときにオブジェクト参照の値をテストする一種のread barrier)をコレクタが使うところで、それによって主要なコレクタの不変性を強制する。 事実上、loaded value barrier(LVB)は、ミューテータがそれらを見る前に、すべての参照が「健全な状態」であり、この強力な保証はアルゴリズムの相対的な単純化を担っている。

loaded value barrier はアプリケーション上に見えるすべての参照に二つの重大な性質を強制する:

  1. マークフェーズが進行中なら、参照状態は「マークが通った」ことを示す(マークフェーズの説明は以下を参照)。
  2. 参照は移動済みのオブジェクトを指さない。

どちらの条件も満たさない場合、loaded value barrierは「トラップ状態」の引き金となるだろう。 そして、コレクタは、アプリケーションコードにそれが見える前に、必要な不変式を固く守るため、参照を修正するだろう。 self healingトラッピング(下記参照)と組み合わせたloaded value barrierテストを使うことは、アプリケーションのスレッドが生存参照をコレクタの到達できないところに退避させる可能性を排除し、マーキングの安全なシングルパスを保証します。 同一のバリアとトラッピングメカニズムの組み合わせは、コレクタのコンパクションや再配置が完了するのを待つ必要がなく、アプリケーションスレッドのオブジェクト参照が常に適した値を参照することを確実とさせ、怠惰な、オンデマンドの、並行コンパクション(オブジェクト移転と参照再配置のどちらも)を支持する役割を果たす。

“Self Healing”

GPGCの並行コレクションのキーは「トラップ状態」のバリアで対処されることで成るSelf Healingの性質である。 loaded value barrierテストが、読み込まれた参照の値がアプリケーションのコードが進行する前に必ず変更されると示した場合、読み込まれた参照の値と、それが読み込まれたメモリ域の値は、現在の不変条件をコレクタが順守するため、どちらも変更されるでしょう(例えば、参照がすでにマークが通り過ぎた、もしくは新しいオブジェクトの位置へマッピングし直すことを示すこと) ソースメモリ域にあるトラップの原因を修正することで(loaded value barrierのようなリードバリアがソースアドレスを傍受することによってのみ可能)、GCトラップは「Self Healing」効果を持つ: 同じオブジェクトへの参照は、このための追加のGCトラップや、他のアプリケーションスレッドを再び引き起こすことはないだろう。 これはマークフェーズの仕事量を限りあるものにし、予測可能にすることを、移転・再配置フェーズと同じように確実にする。 Azulは「Self healing」という表現を2005年にpauselessGCを初めて公表したときに造語し、Teneは「Self healing」という一面がAzulコレクタをユニークなものにしていると信じている。

Azulは、特別に作ったVegaプロセッサではじめに行ったのと同じものを、現代のx86プロセッサで論理的なLVBテストとして実装している。 AzulのVegaハードウェアでは、LVBテストはGC-compactedページ用の特別な仮想記憶保護と同じく参照メタデータのビット・フィールド点検を含んでいる。 さらに、Azulのハードウェアは、コレクタの現在の不変条件と矛盾する値が読み込まれるとき、またテストされるときの、「遅いパス」のトラップ状態を対処するためのユーザモードの高速な多くのトラップハンドラのサポートする。 それらの「遅いパス」のトラップハンドラはそれに入ってから、少しのクロックサイクル(4-10, 時と場合によるが)で抜けることができ、それらはGCアルゴリズムのそのような状態を処理するために使用される - これらは「Self Healing トラップ」だ。 現代のx86-64ハードウェアでは、同じLVBの効果がexecution streamの命令の組み合わせを使うことと、仮想記憶マッピングの適切な操作によって達成される(付録Aを参照)。

Azulアルゴリズムの動作

Azulアルゴリズムは下のイラストのような3つの論理的なフェーズで実装される。

http://www.infoq.com/resource/articles/azul_gc_in_detail/en/resources/azul_gc2small.jpg

  1. マーク: 定期的にマークビットを更新する責任を持つ。
  2. 移転: ほとんどの最近の利用可能なマークビットは少ない生存データをもつページを見つけるために使われ、それらのページは移転され圧縮されたあとで、物理メモリが解放される。
  3. 再配置: あらゆるヒープ内の移転済みポインタを更新(フォワード)する。

マークフェーズ

AzulGCマークフェーズはDavid Baconら(PDF document)によって、「正確な波面」マーカ、SATBではなく、リードバリアによって増幅されるもの、と特徴付けられている。 マークフェーズでは「マークが通ってない」(not marked through:NMT)状態のオブジェクトへの参照を取り込み、追跡する。 各参照のメタデータのビットによってNMT状態は追跡される。そして、loaded value barrierは各参照の状態が現在のGCサイクルの expected-NMT状態とマッチするかを検証する。 loaded value barrierによってNMT状態を強制された不変条件は、生存した参照がアプリケーションスレッドによってコレクタの範囲から逃げる可能性を排除し、コレクタが堅牢で効率的な単一パスのマーキングするのを保証する。

マークフェーズのはじめは、アプリケーションスレッドのすべてのオブジェクト参照を含むルートセットとともに、マーカーの仕事リストが「準備される」。 すべてのマーカでは一般的に、ルートセットは通常CPUレジスタとスレッドのスタック上のすべての参照が含まれている。 ブロックされた(もしくは立ち往生している)スレッドがコレクタのマークフェーズスレッドによって並列にマークされる間、動作中のスレッドは自らのルートセットをマークすることによって協調する。

グローバルな stop-the-world safepoint (すべてのアプリケーションスレッドが同時に停止している場所)を使用するのではなく、マーカーのアルゴリズムは「チェックポイント」メカニズムを利用する。 Azul 2005 VEE "Pauseless GC Algorithm" 論文ではこのように説明されている:

ルートセットがマークされた(そして、expected-NMTは切り替わった)あと、それぞれのスレッドはすぐに進むことができる。
しかし、すべてのスレッドがチェックポイントを渡るまで、マークフェーズは進行することができない。

(注記: この論文はAzulのウェブサイトで長いこと利用できなくなってる)

ルートセットがすべてマークされた後、アルゴリズムは並行・並列マーキングフェーズを継続する。 生存参照が仕事リストから取り出され、それらのターゲットオブジェクトを生存としてマークし、内部の参照は再帰的に進める。

マーキングする仕事リストに沿った通常のコースの中で、マーカー自身のスレッドによって見つけられる参照に加えて、自由に実行中のミューテータスレッドは参照を見つけ、キューを作ることができる。その参照とはミューテータスレッドがメモリから値を読み込むときに遭遇するかもしれない、「マークが通った」と予測されるNMTビットが設定されていない参照。 そのような参照の読み込みは、loaded value barrierのトラッピング状態の引き金となります。その時点では、問題の参照がコレクタによってトラバースされて、正しくマークが通ることを保証するために、その参照をキューにいれる。 したがってNMT値は、マークが通ったと考えることができるよう、すぐに直され、癒される(オリジナルのメモリ域に)。そして、さらなるLVB状態のトリガーを避ける。

マークフェーズはマーカーの仕事リスト内のすべてのオブジェクトが枯渇するまで継続し、終了した時点で、すべてのオブジェクトはトラバースされた状態になる。 マークフェーズ終了時、死んでいるとみなされたオブジェクトだけが「生存」のマークが付かず、すべての有効な参照には「マークが通過した」というNMTビットを持つ。

また、GPGCマークフェーズが soft, weak, そして, phantom referencesを並行に処理することに関しても注意する価値がある。 この性質は アプリケーションによって使用される soft また weak referencesの量に対し、相対的に影響を受けづらい。

移転と再配置フェーズ

移転

移転フェーズは、オブジェクトが移動し、ページが回収されるところだ。 このフェーズ中、いくらかの死亡オブジェクトを含むページは、並行な移転によってページ中に残っている生存オブジェクトを他のページに移転し、すべて未使用な状態に変わる。 これらの移転されたオブジェクトへの参照は、すぐに新しいオブジェクトの場所を指すように再配置されない。 その代わり、loaded value barrierの強制する不変条件を信頼することで、次の再配置フェーズが完了(もう再配置が必要な参照はない)するまでの間、移転の後の参照の再配置は遅れて、並行に行うことが可能である。

移転中、ページのセット(もっともまばらなページからはじまる)は移転と圧縮用に選択される。 セット内の各ページはミューテータのアクセスから保護されており、ページ内のすべてのオブジェクトがコピーされ、連続する圧縮されたページに移転される。 移転されたオブジェクトの位置を追跡するためのフォワーディング情報は、元のページの外側に保持され、並行の再配置によって使用される。

移転中および移転後は、ミューテータが移転されたあとのオブジェクトへの参照を使おうと試みるとき、それは途中で捕まえられ、修正される。 ミューテータによるそのような参照を読み込む試みはloaded value barrierのトラップ状態の引き金になるだろう。その時点では古い参照は適切なオブジェクト位置へ指し直すように修正され、参照が読み込まれた元のメモリ上の値は、LVB状態の将来の引き金にならないように、直されるだろう。

「クイックリリース」

Teneが「クイックリリース」と呼ぶ機能を使用して、GPGCコレクタは再配置を待つことなく、すぐにメモリのページのリソースを再利用する。 元ページの外側にすべてのフォワーディング情報を保持することで、コレクタはページの内容が移転された後(再配置が完了する前)すぐに安全に物理メモリを解放する。 圧縮されたページの仮想メモリ空間は、そのページへの古い参照がヒープ内に残っている限り、解放できない(次の再配置フェーズの終了時にのみ確実に真になる)。 ただし、そのページの裏にある物理メモリリソースは再配置フェーズによってすぐにリリースされ、必要に応じて新しい仮想メモリ域で再利用される。 物理的なリソースのクイックリリースは新しいオブジェクトのアロケーション、およびコレクタ自身の圧縮パイプラインに使用される。 クイックリリース機能に従った「hand over hand」コンパクションを使うことで、コンパクションによってリリースされた1ページのページリソースは、追加されたページを圧縮するための圧縮の宛先として使用され、コレクタは空のメモリを目標として圧縮することなく、単一のパスでヒープ全体を圧縮できる。

再配置

移転フェーズに続く再配置の間は、GCスレッドはオブジェクトグラフをトラバースし、ヒープ内のすべての生存オブジェクトのloaded value barrierテストによって、参照の再配置を完了する。 移転後のオブジェクトを指す参照を見つけると、それを正しいオブジェクトの位置の参照に変える。 いったん再配置フェーズが完了すると、生きていないヒープの参照が存在できる。これらは前の移転フェーズによってプロテクトされたページを参照するだろう。その時点でそれらのページの仮想メモリは解放されている。

再配置フェーズはマークフェーズの対象となるものと同じ生存オブジェクトグラフをトラバースし、コレクタは再配置フェーズを完了させることを急がないので、二つの論理上のフェーズは実際には一つの実装にまとめられる。これは「合同マーク・再配置フェーズ」として知られている。 各合同マーク・再配置フェーズでは、前の移転フェーズによって影響を受けた参照の再配置するのと同時に、次の移転フェーズによって使用される生存オブジェクトの発見とマーキングを実行するだろう。

従来のGCとの比較

HotSpotのConcurrent Mark Sweep (CMS)はmostly concurrent collectorです。 これはいくらかのGCの操作はプログラム実行と並行に動作しますが、いくらかの操作は長い stop-the-world となる。 これについて Tene は以下のように述べている:

"HotSpotのCMSコレクタはすべてのパスにおいて新世代を圧縮する、stop-the-worldな並列新世代コレクタを使う。
 なので、新世代コレクションはとても効率的で、この stop-the-world パスは大体はとても素早く終了する(100msecかそれ以下くらい)。
  
 CMSは旧世代に対しmostly-concurrent collectorを使う。
 これは 大部分の並行性と、ミューテータが動いている間にマークを行うマルチパスなマーカを持っており、変更されたオブジェクトもトラックする。
 CMSはこれらの変更されたオブジェクトを再度訪れ、もうマークを繰り返す。
 CMSは最後の stop-the-world でミューテータに「追いつく」マーキングを実行し、その stop-the-world の際にすべての weak ref と soft ref を処理する。
  
 CMSは並行にコンパクションしない。
 代わりに、CMSは並行なスイープを行い、free-listを維持し、旧世代のアロケーションはこのfree-listを使って満足させるよう試みる。
 しかし、free-listは圧縮されていないので、空き領域は必然的に断片化し、結局CMSは full stop-the-world のヒープ圧縮を行う。"

Java7に搭載されることが予想されている、オラクル社の実験的なGarbage First(G1)コレクタは、snapshot-at-the-beginning(略してSATB)の concurrent marking algorithm とヒープ内の一部を圧縮する stop-the-world のコンパクションを使用した、インクリメンタルなコンパクティングコレクタである。 G1はユーザに停止時間のゴールを設定することを許容し、これらの入力されたゴールはマークフェーズ中のそれぞれのフェーズで行ったヒープ空間内の限られた量の圧縮と、それぞれのフェーズでスキャンを必要とするヒープ内の参照の量の、トポロジー情報に従って使用される。

その実験的な状態から、Teneは「それはまだ現実的なアプリケーションで動かして行くには早い」と述べている。 Teneによれば、CMS のフラグメンテーション処理上の漸次的な改善をG1は意図している一方で、これのコンパクションはいぜんとして完全な stop-the-world の状態で動作し、これのインクリメンタルなコンパクション停止の長さを制限する能力はアプリケーションの固有のヒープトポロジーとオブジェクトの関連性に強く依存しているとのこと。 アプリケーションにともなう人気なオブジェクト、もしくは人気なヒープの一部は、ヒープ全体の参照を再配置の為にスキャンする際に長いstop-the-worldの停止を悩ませだろう。そして、これらの長い停止は、頻発はしないものの、現在のCMSコレクタのものより短くならないだろう。 Teneはこのように述べた:

"コレクタがstop-the-worldの状態で全体のヒープスキャンを行うコードを含む場合、そのコードは同じ場所で動作することが意図されており、
 そしてアプリケーションはいずれはそのような停止を経験することを予測しなければならない。"

G1は確定性の改善を試みているが、G1はそれを保証できていない。 Tene曰く:

"G1はユーザに停止時間のゴールを設定させるが、しかし、G1はそれらのゴールを満たすように「試みる」だけだ。
  
 たとえOSのスケジューリングが完璧だったとしても、個々のコンパクションの停止を含むG1の能力は本質的にヒープの形状にたいして敏感である、といった確定性を保証できない。
  
 人気オブジェクトと(もっとう重要な)人気ヒープリージョン(このリージョンは他の多くのリージョンから参照されているが、
 個々の人気のオブジェクトを持つ必要はないもの)は、それらがコンパクションされる場合に、単独停止の完全なヒープコンパクションをそのうちG1に引き起こす。
 現実的なアプリケーションのパターン(LRUキャッシュがゆっくりかくはんされ、多くのキャッシュがヒットするようなもの)はヒープの圧倒的多数のそのようなヒープ関連性から、
 単独停止の効率的な完全のコンパクションスキャン再配置を強制的に動作させてしまうことを、明らかにするだろう。"

前のInfoQの記事ではもっと技術的な詳細を述べている。

結論

Zingの導入により、Azulは商用ハードウェア上の純粋なソフトウェア上で可能なポーズレスGCを作り、これはより消費者的で、採用を簡単にしてくれる。 ポーズレスGCができることは:

  1. Javaインスタンスを豊富に、安く、一般的なハードウェアで効率的に作り使えることを許します。それはすでに利用可能であり、未来のハードウェアの改善に伴って成長していく。
  2. アーキテクトと開発者は自身の設計の中でメモリ容量を積極的に使うことができ、大量のin-memory、in-processなキャッシング、また完全なin-memoryの表現を新しい問題に対して利用できる。
  3. 開発者は現在のGCに過敏なシステムの微調整、そして再調整をさけられる。

補遺A: 最新のIntelとAMDプロセッサ上でのアルゴリズムの実装

GPGCアルゴリズムははじめAzulのVegaシステム上で開発され、2005年のはじめての市販化から発展し、成熟した。 これは現在、VegaとX86-64のアーキテクチャで利用可能である。 Vegaのカスタムプロセッサ上では、GPGCは特別な loaded value barrier (LVB) 命令をバリアチェックとして使う。 IntelとAMDプロセッサの仮想化に沿った最近の改善は、AzulがIntelとAMDベースのサーバにおいても同等の性能をもたらした。 シングルサイクルLVB命令は現在のx86-64アーキテクチャに存在しませんが、AzulはそれのJITコンパイラにてx86の命令として意味的に同等の命令セットを作り、効率的に通常の命令列に対して挟み込む機能を使用する。 具体的にIntelでは、AzulはEPT(Extended Page Table)の機能(これはIntelのXenon 55xx系で最初に登場し、後のXeon 56xx, 65xx 75xxチップにもある)、そして、AMDではNPT(AMD-V Nested Pagingとしてもしられる)機能を利用する。 これは再配置用のx86仮想メモリサブシステムとGCが圧縮したページの保護を連携して実施し、それらによって loaded value barrier と同等の効果を達成し、Pauseless GCアルゴリズムが実行するのに必要なアルゴリズム的な不変性を維持する。 loaded value barrierの命令セットはJITコンパイラによって発行され、効率的に通常の命令列に挟み込まれる。

x86で「高速なトラップ」をシュミレートするため、Azulは、すべてのホットケース内の単一の条件分岐を使用して、意味的に等しく動作するテストのセットと、loaded value barrierの場所で条件付き呼び出しを行うx86の命令列を注入する。 Teneはx86の命令を使った「LVB」の命令は「マイクロコード」と類似していると表現した。

"x86の命令はJITコンパイラによって注入される。
 そして、我々は nice wide 4-issue、アウト・オブ・オーダー実行、最新のx86の投機的パイプライン実行をうまく隠すオペレーションセットを選んだ。
(最新のNehalemコアでは、たとえば、 128-u-op の深さのリオーダバッファを持ち、
 36 reservation stations、combined with macrofusion、loop streaming、そしてその他のクールな機能を持っている)。"

例の議論はここで見ることができる。

Teneはホットコードがどのようにパイプラインが効率的に利用できるかついてのいくらかの詳細を提供し続けた。

補遺B: GCのテスト方法の提案

この記事の間、我々はミューテーションレートや弱参照、オブジェクトの生存期間といったGCが過敏になる様々な要素を見てきた。 しかし、GCアルゴリズムのあなたの利用とは関係なく、これはあなたのアプリケーションが興味深いGC動作中にどのように機能するかを検証するために重要なことだ。 負荷試験の間、経験不足のチームはしばしばGCが動かないように調整してしまうだろう。 しかし、それは現実世界のできごとからは回避されていない。

Teneは少ない負荷をかけることで、数日または数時間相当(短いが実践的なテスト時間)に興味深いGCのイベントを詰め込むことが出来るような特別なテストの設計を提案した。 バックグラウンドLRUキャッシュのような、これのためのよいいくつかのテクニックはある。 しかし、もっとも簡単で、もっとも効率的なものの一つは、Teneが作ったAzul Websiteから無料で利用できるFraggerのような、シンプルなフラグメンテーションジェネレータだ。

Fraggerは与えられたサイズのオブジェクトの巨大なセットの生成を繰り返し、それらのセットを生きた残りの小さなセットに刈り込み、そしていくらかの圧縮された部分を除いた、前のパスで解放されたオブジェクトが、解放された領域に収まるように、パスの間でオブジェクトサイズを増加させるような働きをする。 Fraggerはそれらを刈り込む前に、新世代コレクタによる人工的な早めのコンパクションの可能性を排除するために、オブジェクトセットに歳を取らせる。 通常のセッティングで動かしたとき、Fraggerは全体のヒープ領域の~25%を占め、20MB/秒でオブジェクトを確保する。

概要の考えは、我々が感度のために検討した測定基準をテストすることになっており、これはあなたのアプリケーションがよく動く範囲を確立する。 これを行うため、アプリケーションがそれらを生み出すための処理を増やすことであなたのコントロール下に表示される測定基準を変える。 たとえば次のようなことができる:

アプリケーションがパフォーマンス要件を満たして中断するまで、これらを実行することで、アプリケーションが動作することができるギリギリのポイントを、あなたは見つけることができる。

翻訳者コメント

翻訳は、中村 成洋<authornari _at_ gmail.com> が行ないました。

もしも、誤植や誤訳などがありましたらお気軽に @nari3までお知らせください。

Copyright (C) 2011 中村成洋