Javaの超低レイテンシなGCアルゴリズム、ZGCをコンパイルして動作を試す

The Z Garbage Collector

以下の資料を見てZGCのことを知りました。

The Z Garbage Collector

ZGCは、 "A Scalable Low Latency Garbage Collector" というものだそうで、まだ開発中でリリースはされていないです。

f:id:kuro_m88:20180218013120p:plain

数TBまでのヒープメモリのサイズを想定していて、なおかつGCの最大停止時間が10msというのがゴール。既にヒープサイズが128GBのベンチマークにおいて、パラレルGCやG1GCより圧倒的に停止時間が短い。ベンチマーク上では最大停止時間も10msを切っています。 アルゴリズムについては別途まとめてみようかと思っています。Colored Pointerという概念が特徴です。

f:id:kuro_m88:20180218013454p:plain

ZGCを試す

開発版のZGCは既に試すことができるようです。ですが、自分でビルドする必要があります。紹介したスライドで書かれている方法ではうまくいかなかったのですが、なんとか動かすところまでできたので紹介します。

環境はUbuntu16.04.3で、24CPU、48GBメモリのサーバで作業しました。メモリは8GBもあればビルドできるのではないかと思います。

必要なパッケージをインストール

ubuntu@java-z:~$ sudo apt install build-essential mercurial zip unzip libx11-dev libxext-dev libxrender-dev libxtst-dev libxt-dev libcups2-dev libfontconfig1-dev libasound2-dev

ZGCプロジェクトのリポジトリをダウンロードしてくる

3GB以上あるので、そこそこ待たされます。

ubuntu@java-z:~$  hg clone http://hg.openjdk.java.net/zgc/zgc

configureする

ubuntu@java-z:~$ cd zgc
ubuntu@java-z:~/zgc$ sh ./configure

[...略]

checking for javac... no
checking for java... no
configure: Could not find a valid Boot JDK. You might be able to fix this by running 'sudo apt-get install openjdk-8-jdk'.
configure: This might be fixed by explicitly setting --with-boot-jdk
configure: error: Cannot continue

openjdk-8-jdk をインストールしろというメッセージが出ますが、実際に必要なのは JDK9 です。ここでOpenJDK 9をインストールしたくなりますが、後述する理由ビルドがコケます。

=== Output from failing command(s) repeated here ===
* For target buildtools_interim_langtools_modules_java.compiler.interim__the.BUILD_java.compiler.interim_batch:
javac: invalid flag: --module-path
Usage: javac <options> <source files>
use -help for a list of possible options
javac: no source files
Usage: javac <options> <source files>
use -help for a list of possible options

javacに --module-path というオプションがないというエラーです。実はこれ、OpenJDKとOracle JDKでコマンドオプションの引数の名前が違うせいです。このまま通るようにするにはOracleのJDK9をインストールする必要があります。

ubuntu@java-z:~/zgc$ sh ./configure

[...略]

Configuration summary:
* Debug level:    release
* HS debug level: product
* JDK variant:    normal
* JVM variants:   server
* OpenJDK target: OS: linux, CPU architecture: x86, address length: 64
* Version string: 10-internal+0-adhoc.ubuntu.zgc (10-internal)

Tools summary:
* Boot JDK:       openjdk version "9-internal" OpenJDK Runtime Environment (build 9-internal+0-2016-04-14-195246.buildd.src) OpenJDK 64-Bit Server VM (build 9-internal+0-2016-04-14-195246.buildd.src, mixed mode)  (at /usr/lib/jvm/java-9-openjdk-amd64)
* Toolchain:      gcc (GNU Compiler Collection)
* C Compiler:     Version 5.4.0 (at /usr/bin/gcc)
* C++ Compiler:   Version 5.4.0 (at /usr/bin/g++)

Build performance summary:
* Cores to use:   24
* Memory limit:   48287 MB

うまくいくと、上記のような出力が出るはずです。

makeする

ubuntu@java-z:~/zgc$ make images

ビルドが終わるまで待ちます。自分の環境だと4分半くらいで終わりました。

f:id:kuro_m88:20180225224416p:plain

CPUが全コアほぼ100%になった。

javaのバージョンの確認

ubuntu@java-z:~$ cd zgc/build/linux-x86_64-normal-server-release/images/jdk/bin
ubuntu@java-z:~/zgc/build/linux-x86_64-normal-server-release/images/jdk/bin$ ./java -version
openjdk version "10-internal" 2018-03-20
OpenJDK Runtime Environment (build 10-internal+0-adhoc.ubuntu.zgc)
OpenJDK 64-Bit Server VM (build 10-internal+0-adhoc.ubuntu.zgc, mixed mode)

ZGC開発用のJava10がビルドされたのがわかります。

ZGCを使ってみる

100KB程度のオブジェクトを大量に生成するだけのプログラムを作りました。

import java.util.Arrays;

class Data {
    byte[] data;

    Data() {
        byte[] array = new byte[100000];
        Arrays.fill(array, (byte)1);
        this.data = array;
    }
}

public class Hello {
    public static void main(String[] args){
        System.out.println("########## START ##########");
        for(int i =0; i < 1000; i++) {
            new Data();
        }
        System.out.println("########## END ##########");
    }
}

コンパイル

ubuntu@java-z:~$ zgc/build/linux-x86_64-normal-server-release/jdk/bin/javac Hello.java

実行

-XX:+UseZGC をつけるとZGCが有効になります。-Xlog:gc* で詳細なGCのログが出ます。 ヒープサイズを最大100MBまでに制限して実行してみましょう。

ubuntu@java-z:~$ zgc/build/linux-x86_64-normal-server-release/jdk/bin/java -Xmx100M -XX:+UseZGC -Xlog:gc* Hello
[0.006s][info][gc,init] Initializing The Z Garbage Collector
[0.006s][info][gc,init] Version: 10-internal+0-adhoc.ubuntu.zgc (release)
[0.006s][info][gc,init] NUMA Support: Enabled
[0.006s][info][gc,init] NUMA Nodes: 2
[0.006s][info][gc,init] CPUs: 24 total, 24 available
[0.006s][info][gc,init] Memory: 48287M
[0.006s][info][gc,init] Large Page Support: Disabled
[0.006s][info][gc,init] Workers: 15 parallel, 3 concurrent
[0.010s][info][gc,init] Pre-touching: Disabled
[0.010s][info][gc,init] Pre-mapping: 100M
[0.035s][info][gc     ] Using The Z Garbage Collector
[0.187s][info][gc,start] GC(0) Garbage Collection (Warmup)

[略...]

[0.483s][info][gc,ref    ] GC(7) Clearing All Soft References
[0.483s][info][gc,start  ] GC(7) Garbage Collection (Allocation Stall)
[0.483s][info][gc,phases ] GC(7) Pause Mark Start 0.141ms
[0.485s][info][gc,phases ] GC(7) Concurrent Mark 2.015ms
[0.486s][info][gc,phases ] GC(7) Pause Mark End 0.667ms
[0.486s][info][gc,phases ] GC(7) Concurrent Process Non-Strong References 0.133ms
[0.486s][info][gc,phases ] GC(7) Concurrent Reset Relocation Set 0.054ms
[0.486s][info][gc,phases ] GC(7) Concurrent Destroy Detached Pages 0.001ms
[0.486s][info][gc        ] Allocation Stall (main) 3.605ms
[0.490s][info][gc,phases ] GC(7) Concurrent Select Relocation Set 3.574ms
[0.490s][info][gc,phases ] GC(7) Concurrent Prepare Relocation Set 0.289ms
[0.491s][info][gc,phases ] GC(7) Pause Relocate Start 0.519ms
[0.491s][info][gc        ] Allocation Stall (main) 1.226ms
[0.493s][info][gc,phases ] GC(7) Concurrent Relocate 1.953ms
[0.493s][info][gc,load   ] GC(7) Load: 0.61/0.70/0.51
[0.493s][info][gc,mmu    ] GC(7) MMU: 2ms/16.2%, 5ms/52.6%, 10ms/75.0%, 20ms/84.0%, 50ms/88.6%, 100ms/93.2%
[0.493s][info][gc,marking] GC(7) Mark: 8 stripe(s), 1 proactive flush(es), 1 terminate flush(es), 0 completion(s), 0 continuation(s)
[0.493s][info][gc,reloc  ] GC(7) Relocation: Successful, 1M relocated
[0.493s][info][gc,nmethod] GC(7) NMethods: 128 registered, 0 unregistered
[0.493s][info][gc,ref    ] GC(7) Soft: 153 encountered, 19 discovered, 19 dropped, 0 enqueued
[0.493s][info][gc,ref    ] GC(7) Weak: 147 encountered, 78 discovered, 78 dropped, 0 enqueued
[0.493s][info][gc,ref    ] GC(7) Final: 0 encountered, 0 discovered, 0 dropped, 0 enqueued
[0.493s][info][gc,ref    ] GC(7) Phantom: 3 encountered, 3 discovered, 3 dropped, 0 enqueued
[0.493s][info][gc,heap   ] GC(7)                Mark Start          Mark End        Relocate Start      Relocate End           High               Low
[0.493s][info][gc,heap   ] GC(7)  Capacity:      100M (100%)        100M (100%)        100M (100%)        100M (100%)        100M (100%)        100M (100%)
[0.493s][info][gc,heap   ] GC(7)   Reserve:       62M (62%)          62M (62%)          62M (62%)          62M (62%)          44M (44%)          62M (62%)
[0.493s][info][gc,heap   ] GC(7)      Free:        0M (0%)            0M (0%)            0M (0%)            0M (0%)           10M (10%)           0M (0%)
[0.493s][info][gc,heap   ] GC(7)      Used:       38M (38%)          38M (38%)          38M (38%)          38M (38%)          56M (56%)          28M (28%)
[0.493s][info][gc,heap   ] GC(7)      Live:         -                 1M (2%)            1M (2%)            1M (2%)             -                  -
[0.493s][info][gc,heap   ] GC(7) Allocated:         -                 0M (0%)           12M (12%)          38M (38%)            -                  -
[0.493s][info][gc,heap   ] GC(7)   Garbage:         -                36M (36%)          24M (24%)          16M (16%)            -                  -
[0.493s][info][gc,heap   ] GC(7) Reclaimed:         -                  -                12M (12%)          20M (20%)            -                  -
[0.493s][info][gc        ] GC(7) Garbage Collection (Allocation Stall) 38M(38%)->38M(38%)

[略...]

[0.528s][info][gc,heap,exit] Heap
[0.528s][info][gc,heap,exit]  ZHeap           used 30M, capacity 100M, max capacity 100M
[0.528s][info][gc,heap,exit]  Metaspace       used 6395K, capacity 6442K, committed 6656K, reserved 8192K

こんな感じでGCのログが出ました。これでZGCをベンチマーク取ったりして試して遊べますね!