NHN Cloud NHN Cloud Meetup!

再確認、JDK 12のもたらしたもの

Java 12

2019年3月19日にGA配布されたJDK 12の主な機能です。

  • JEP189:Shenandoah:A Low-Pause-Time Garbage Collector(エクスペリメンタル)
  • JEP230:Microbenchmark Suite
  • JEP325:Switch Expressions(プレビュー)
  • JEP334:JVM Constants API
  • JEP340:One AArch64 Port、Not Two
  • JEP341:Default CDS Archives
  • JEP344:Abortable Mixed Collections for G1
  • JEP346:Promptly Return Unused Committed Memory from G1

出典:https://openjdk.java.net/projects/jdk/12/

注目機能

(JEP189)Shenandoah:A Low-Pause-Time Garbage Collector(エクスペリメンタル)

Shenandoahという名前のGC(ガベージコレクター、Garbage Collector)アルゴリズムが新たに追加されます。実行中のJavaスレッドと同時にGCを実行し、GCの停止時間を短縮するアルゴリズムです。ヒープサイズとは関係なく同じ停止時間を維持します。

GCはそれぞれのアルゴリズムで追求しているものが異なります。このアルゴリズムはスループットとメモリ空間の効率性よりもレスポンスに焦点を当てています。したがって、リアクティブを求めるアプリケーションに適したアルゴリズムです。しかしながら、GC以外で発生する(例. TTSP:Time To Safe Point)問題については、このJEPの範囲外とされています。

Shenandoahは以下のような段階を経て実行されます。


出典:https://wiki.openjdk.java.net/display/shenandoah/Main

  1. Initial Marking
  2. Concurrent Marking
  3. Final Marking
  4. Concurrent Compaction

Stop The World(STW)の後、Root setをスキャンして、Javaスレッドと同時にMarkingを実行します。その後、もう一度Stop The Worldを実行して、Final Markingを実行し、最後の圧縮段階でMarkingされたオブジェクトを削除します。全体の流れはCMS GCと似ています。

Safe Point:JITコンパイラが実行を中断するコード内のイベントや位置
Stop The World:GCを実行するためGCスレッドを除くすべてのスレッドが停止されている状態
Root set:Objectの参照を列挙された形で持っている束

(JEP230)Microbenchmark Suite

JDKにデフォルトでマイクロベンチマーク製品が追加されます。追加されたマイクロベンチマークを使って、既存のマイクロベンチマークを実行したり、新しいマイクロベンチマークが簡単に作成できます。追加されたマイクロベンチマークは、JMH(Java Microbenchmark Harness)を基準にしています。

ベンチマークはパフォーマンスを比較するための数値化を意味します。JMHはそのうち、JVMから戻るプログラムのパフォーマンスをテストするためのベンチマークです。作成方法は、こちらをご参照ください。

(JEP325)Switch Expressions(プレビュー)

従来提供されていたswitch文が、JDK 14からPreviewで提供されるinstance ofのパターンマッチング(JEP 305)を使用できるように簡略化された表現に変更されました。この変更はJDK 12ではPreviewとして、実際の適用はJDK 13からJEP 354に適用されます。

switch文は、基本的に上から下へ流れる(Fall Through)形式で動作します。よって、途中でbreak;で中断し以降の条件が適用されないように制御文を追加する必要があります。この方法は低レベルの言語では便利ですが、高水準のコンテキストではエラー発生の可能性が高くなります。誤ってbreak;が欠落している場合は、意図せぬコードが動作し、途中でbreak;を入れなければならないため、コードが長くなりデバッグが難しくなるからです。(IntelliJでもwarningが表示され、if-elseに変更するように誘導されます)

switch (day) {
    case MONDAY:
    case FRIDAY:
    case SUNDAY:
        System.out.println(6);
        break;
    case TUESDAY:
        System.out.println(7);
        break;
    case THURSDAY:
    case SATURDAY:
        System.out.println(8);
        break;
    case WEDNESDAY:
        System.out.println(9);
        break;
}

以下のように作成すると一層見やすく、不要なコードも減り、意味にも合わせて作成することができます。

switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
    case TUESDAY                -> System.out.println(7);
    case THURSDAY, SATURDAY     -> System.out.println(8);
    case WEDNESDAY              -> System.out.println(9);
}

もし、switch文の内部でcase別に異なる結果値を出力する必要があれば、どうしますか?switch文で使用するブロックも1つの範囲で指定されるため、外部変数を置き、その変数に割り当てる方法で作成しなければなりません。

int numLetters;
switch (day) {
    case MONDAY:
    case FRIDAY:
    case SUNDAY:
        numLetters = 6;
        break;
    case TUESDAY:
        numLetters = 7;
        break;
    case THURSDAY:
    case SATURDAY:
        numLetters = 8;
        break;
    case WEDNESDAY:
        numLetters = 9;
        break;
    default:
        throw new IllegalStateException("Wat: " + day);
}

新しく提供される表現式ではこのような不要な表現を減らし、次のようにきれいに作成できます。もちろん、caseに該当する式でもブロックを使用できます。

int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY                -> 7;
    case THURSDAY, SATURDAY     -> 8;
    case WEDNESDAY              -> 9;
    default                     -> {
        // このようにも作成できます。
        throw new IllegalStateException("Wat: " + day);
    }
};

また、既存のswitch構文とも混ぜて使えるように提案しています。

int result = switch (s) {
    case "Foo": 
        break 1;
    case "Bar":
        break 2;
    default:
        System.out.println("Neither Foo nor Bar, hmmm...");
        break 0;
};

 

(JEP334)JVM Constants API

struct Class_File_Format {
   u4 magic_number;
   u2 minor_version;
   u2 major_version;
   u2 constant_pool_count;
   cp_info constant_pool[constant_pool_count - 1];
   u2 access_flags;
   u2 this_class;
   u2 super_class;
   u2 interfaces_count;
   u2 interfaces[interfaces_count];
   u2 fields_count;
   field_info fields[fields_count];
   u2 methods_count;
   method_info methods[methods_count];
   u2 attributes_count;
   attribute_info attributes[attributes_count];
}

Javaクラスは上記のような構造で、すべてのクラスはcp_infoと明示された内部のメソッドとクラス、StringのIntegerの値をバイトコード形式で保存するconstant poolがあります。

cp_info {
    u1 tag;
    u1 info[];
}

 

constant poolは、あるメソッドやフィールドを参照するとき、JVMが対象メソッドとフィールドの実際のメモリ上のアドレスを把握するために参照するテーブルです。constant poolの各entryはtagと呼ばれ、各Constant Typeを表す1バイトの値を持っています。(info配列の内容はtagの値によって異なります)

このJEPでは、このようなconstant poolで使えるランタイムアーティファクトとキークラスファイルのnominal descriptionをモデリングするためのAPIを紹介しています。nominal descriptionは値ではなく、ある値を説明したりconstant pool値を格納するための説明書に当たります。このnominal descriptorを使ってクラスファイル内にどのような型の値が入るかを記述します。

追加されたjava.lang.constantパッケージの代表的なnominal descriptorとして、ConstantDescがあります。

(JEP340)One AArch64 Port, Not Two

32ビットARMポートと64ビットAARCH64ポートを維持しながら、ARM64ポートに関連するすべてのソースを取り除きます。

ARM64ビットに関連するソースがJDK内部に重複して含まれており、それぞれsrc/hotspot/cpu/armopen/src/hotspot/cpu/aarch64ディレクトリの下位に入ってました。どちらもAARCH64を実装しますが、ORACLEが寄与したパッケージをARM64と呼ぶそうです。

ORACLEとの依存性をなくすための提案で、AARCH64の詳細はこちらから確認できます。

(JEP341)Default CDS Archives

64ビットプラットフォームにおけるJDKビルドプロセスを改善し、CDS(Class Data Sharing)アーカイブの作成を目指す提案です。32ビットプラットフォームについては、後日追加される予定です。

CDSは以前、Big things in JDK 10で言及しましたが、JVM起動時のパフォーマンスを向上させたり、複数台のJVMが1つの物理デバイスまたは仮想デバイスから戻る場合、リソースへの影響を軽減するために開発された機能です。

ここでは、ユーザーが直接実行する必要はなく、-Xshare:dumpオプションからCDSを使用できるように提案しています。また、JDK 11のVMには-Xshare:autoが基本使用されるように設定されているため、CDSのメリットが利用できます。これを使用しない場合は、-Xshare:offコマンドを使用しましょう。

java -Xshare:off HelloWorld.java


(JEP344)Abortable Mixed Collections for G1

GC(Garbage Collection)の1つであるG1が、効率的に動作するように中断可能なCollectionを持つように変更する内容です。

GCが発生した場合、STW(Stop The World)という名前の動作で不要なデータを収集できる時間を持ちますが、この時間は一般的にアプリケーションの動作の中間に入る時間です。この時間が長くなるほどアプリケーションは遅くなります。したがって、決められた時間内に不要なオブジェクトを収集できない場合は、GCを中断できるようにする提案です。

GC対象を80%の必須対象(Mandatory)と20%の選択対象(Optional)に分け、優先的に必須部分から収集を行います。その後、残りの時間で選択部分で収集を進行し、残りの停止時間に選択部分が収集できないと判断すると、これを次のGC時間の選択部分に渡します。よって、ヒューリスティックスと呼ばれ、必須部分と選択部分を区別する予測が正確なほど選択部分は減少します。

(JEP346)Promptly Return Unused Committed Memory from G1

同様にG1の改善ですが、GCが有効な状態でJavaのヒープメモリをオペレーティングシステムに返すという内容です。

現在、G1はFull GCが起きたり、Concurrent cycleという状況でのみ、Javaのヒープメモリをオペレーティングシステムに返します。しかし、Full GCはJavaで最大限回避すべき状況のため、Concurrent cycleのみ対象の返還作業ができるにも関わらず、外部で強制しない限りほとんどの場合でヒープメモリを返しません。

これらの動作は、ハイパーバイザのリソースを共有して使用するコンテナ環境において特に不利になります。VMが無効な場合には、そのVMに割り当てられたメモリの一部を使用している段階でも、G1はすべてのヒープメモリを維持することになり、VMを使用しているユーザーは不要なメモリを、またリソースを提供するクラウドプロバイダーはハイパーバイザーのリソースを、すべて活用できなくなります。したがって、VMの活動状態を検出してヒープの使用量を調整できるなら両方ともメリットがあります。

上記のShenandoahとOpenJ9のGenCon collectorでは、すでに同様の機能を提供しています。
OpenJDKでテストした結果、夜間はメモリを約85%まで削減できました。

その他

Files.mismatch

java.nio.fileパッケージに2つのファイルを比較するためのFiles.mismatch(Path path, Path path2)関数が追加されます。

public static long mismatch(Path path, Path path2) throws IOException

引数として受け取った2つのパスにあるファイルを比較して、最初の異なる部分の位置を返します。同じ場合は-1Lを返します。

NumberFormat.getCompactNumberInstance

LocaleとNumberFormat.Styleによって異なる形式で値を返す関数が追加されます。

NumberFormat format = NumberFormat.getCompactNumberInstance(new Locale())

Collectors.teeing

Stream APIに追加されるCollectors.teeing関数です。2つのCollectorと1つのBiFunctionで合計3つの引数を受け取ります。最初に受け取った2つのCollectorの結果を3つ目のBiFunctionで受けて計算することができます。

double mean = Stream.of(1, 2, 3, 4, 5)
                .collect(Collectors.teeing(
                        summingDouble(i -> i),
                        counting(),
                        (sum, n) -> sum / n));

System.out.println(mean); // 3.0

 

Stringクラス

  • String.indent(int n):文字通り入力したnだけインデントする関数です。負の値を入れる場合は前にマイナスを入れます。
  • String.transform(Function f):Function<span>に入る形式に変換されます。

NHN Cloud Meetup 編集部

NHN Cloudの技術ナレッジやお得なイベント情報を発信していきます
pagetop