Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

Oracle Code One 2018参加記: GC編

Oracle Code Oneは、JavaOneとして2017年まで開催されていたものを、リブランドしたカンファレンスです。

2018/10/22〜25までの4日間、USサンフランシスコのモスコーンコンベンションセンターで開催されました。このブログでは、時系列ではなく、トピックごとに書きます。

この投稿は、参加記の3つ目です。

GCセッション

GCのセッションは、ShenandoahとZGCの2つを聴きました。

  • Shenandoah GC: The Next Generation [DEV5561]
  • ZGC: A Scalable Low-Latency Garbage Collector [DEV6028]

どちらも、録画が公開されていますので、そちらをご覧になるのが一番良いかと思います。

www.youtube.com

www.youtube.com

GCはJVMの構成要素ですので、もちろんGCも好きなのですが、私にとっては、GCの話は理解が難しい分野です。

ShenandoahもZGCも、TB級のヒープで、停止時間を10ms以下にすることを目標にしているGCアルゴリズムです。対価として、アプリケーションのスループットが落ちますが、たとえばZGCでは、それを15%以下に抑えることも、目標の1つとなっています。Shenandoah GCもZGCも、G1GCと同じくリージョンを用いたGCアルゴリズムです。

Shenandoah GC

Shenandoahは、シェナンドー(ア)と読みます。前述の通り、リージョンを用いていますが、G1GCと異なり、young/oldの世代分割はありません。Red Hat社が主導して開発しており、RHELとFedoraで利用できます。現在バージョン2.0が開発中で、Experimentalです。JEPはこちらです。

JEP 189: Shenandoah: A Low-Pause-Time Garbage Collector

ほぼすべてを、コンカレント(並行)に処理することで、停止時間を短くします。そうするには、リージョン間でオブジェクトを移動させる場合(コンパクションのため、liveオブジェクトを別のリージョンに移動させる場合など)に、課題があります。

並行であるため、アプリケーションも動作しています。オブジェクトを移動させるために、オブジェクトをコピーしますが、そのコピー間にアプリケーションがオブジェクトを更新した場合、コピー対象のオブジェクトに不整合が発生します。コピー元、コピー先のどちらかだけを更新してしまうからです。

Shenandoahでは、これを防ぐためにBrooks Pointerを使います。一種の間接化であり、オブジェクトの読み取りは、すべてこのBrooks Pointerを経由して取得します。前述のコピー時には、Brooks PointerをCAS命令でアトミックに更新し、コピー元、コピー先のオブジェクト両方のBrooks Pointerが、新しいコピー先のオブジェクトを参照、更新するようにします。

このため、バリアが必要です。ライトバリアで、アプリケーションが対象リージョンのオブジェクトを更新する際に、更新前にコピーを先に作成するようにします。リードバリアは、前述のように、必ずBrooks Pointerを経由してオブジェクトを参照するようにします。ここにコストがかかりますが、Shenandoahには、代わりにカードテーブルもリメンバーセットもありませんので、それなりのコストで済むようです。

ShenadoahのBrooks Pointerのことは、yodaさんが2016年のJJUG CCC Fallで話されています。私もそのセッションを聴きました。大変わかりやすかったです。日本語のスライドもありますので、ぜひご覧ください。

yoda-jp.hatenablog.com

Shenadoahのことを聴き始めてはや3年ですが、ようやく概要が理解できたように思います。

ZGC

ZGCは、ズィージーシーと読みます。日本では、ゼットジーシーと言われています。現状、ZGCにはyoung/oldの世代分割がありません。ただ、将来的には世代が導入されるかもしれないようです。Oracle社が主導しており、Linux/x86_64のみ使えます。Java 11でExperimentalとして導入されています。JEPはこちらです。

JEP 333: ZGC: A Scalable Low-Latency Garbage Collector (Experimental)

ZGCでは、オブジェクトを別のリージョンに移動させた場合、forwarding tableに記録します。オブジェクトを移動させた際に、その移動したオブジェクトを参照しているオブジェクトは、移動先を参照するように絶対に更新しなければなりませんが、ZGCでは次のGCサイクルまで遅らせます。こういった更新は、オブジェクトグラフをwalkすることになりますが、次のサイクルの最初に、マークフェーズでオブジェクトグラフをwalkするため、ここで合わせて移動したオブジェクトへの参照も更新させます。これにより、オブジェクトグラフのwalk回数を減らせます。

もし次のサイクルのマークフェーズより前に、アプリケーションが、リージョンを移動したオブジェクトを使用しようとした場合、都度参照を更新します。ZGCでは、ロードバリアを使って、そうしたケースに対応します。

オブジェクトをロードした後に、ロードバリアの処理を挿入します。この処理で、オブジェクトの状態をチェックするわけですが、その状態はどこで管理しているのでしょうか?ZGCでは、ポインタで管理します。64ビットポインタの中で使っていない部分のビットに、状態を記録します。そのため、ZGCは64ビットのみ対応しており、CompressedOopsも使えないのです。

このポインタはColored Pointerと呼ばれており、 4ビットがカラーの表現に使われます。1ビットずつ、Marked0、Marked1、Remapped、Finalizableです。この4ビットの状態を見て、必要であればロードバリア内でオブジェクトを正しい位置からロードし直します。もちろん、その分のコストがかかります。

ZGCについては、来月2018/12/15のJJUG CCC 2018 Fallで、chiroitoさんによるセッションがあります。超上級者向けとのことで、セッション概要にターゲットの記述があります。後日スライドを見てみるのも、よいかもしれません。

http://www.java-users.jp/ccc2018fall/#/sessions/6b4aace0-7ff9-4b34-9f40-50ff505e76e7

Oracle Code One 2018参加記

1つ目、2つ目はこちらです。

www.sakatakoichi.com

www.sakatakoichi.com