3.1K Views
September 30, 22
スライド概要
JJUG Night Seminar 2022 September
performance, site reliability engineering mania
Head toward Java 19 KUBOTA Yuji LINE Corporation / IMF Team JJUG Night Seminar 2022/Sep/30
KUBOTA Yuji (@sugarlife) Software Engineer of internal Kafka platform (IMF team) at LINE Corporation OpenJDK Author / IcedTea committer https://www.slideshare.net/YujiKubota/ We're hiring! LINE Engineering Culture (Youtube) エンジニアリングの究極的な挑戦の場はここにある Kafkaインフラを全社に提供す るIMFチームの仕事のやりがい 「あたりまえのことを、あたりまえにやる、“あたりまえ”のレベルが高い開発組 織」を目指す
Java 19 Release 2022/9/20: https://mail.openjdk.org/pipermail/jdk-dev/2022-September/006933.html
リリースサイクル (1/2) リリースサイクル OpenJDKコミュニティのメジャーバージョンのリリースは半年ごと サポート期間はディストリビュータによって異なる LTSの対象バージョンもディストリビュータによって異なる Oracleは3年から2年毎へ変更を提案し、次のLTS(Long Term Support)はJava 21 (Java Chief Architectの記事, Official blog)
リリースサイクル (2/2) よくある誤解 OpenJDKコミュニティからダウンロード -> OpenJDKはバイナリを配布していな いので「LTSのバイナリ」は存在しない jdk.java.netはOracle (タイトルにJDK Builds from Oracleになっている) LTSは全機能が標準機能 -> 試験的機能も盛り込まれる LTSじゃないと性能は悪い -> LTSかどうかに関係なく全バージョンに対して商用に 耐えうる安定性が求められている (OpenJDK ML)
Resources (1/2) Specification: JSR (Java Specification Request) 394 Source code: github: openjdk/jdk、openjdk/jdk19u JEP (JDK Enhancement-Proposal) Java新機能拡張の提案 Java 19では7個 (JBS (Java Bug System bugs.openjdk.org))、(Project page(openjdk.org)) JBSの説明はこちら CSR (Compatibility & Specification Reviews) 非互換性を伴う変更のレビュー(非互換性を伴う変更は出す必要がある) Java 19では171個 (JBS) なお、JSR 394のページからリンクされているJEP、CSRのDashboardはSE(Standard Edition)にscopeを限定しているので、JEPは4個、CSRは108個と全体と比較すると減る
Resources (2/2) Release note: 各製品ごとの変更点 OpenJDKコミュニティの内容 != すべての製品 Java 19では61リリースノート (JBS, java.net) APIリスト API diff from the latest: https://cr.openjdk.java.net/~iris/se/19/build/latest/ New API since JDK 11: https://docs.oracle.com/en/java/javase/19/docs/api/newlist.html リリースサイクル短縮してから初LTSであるJDK 11からの差分が確認可能 その他の非公式自動生成サイト https://builds.shipilev.net/backports-monitor/release-notes-19.html JBSの情報をもとにリリースノートなどが自動生成されている(メンテナンスや バックポートを追跡しやすくするためのサービス) VM Options Explorer: https://chriswhocodes.com/vm-options-explorer.html
「プレビュー」、「インキュベータ」、「試験機能」 Incubator (JEP 11): Java APIの試験用モジュール( jdk.incubator )。標準化に向けてフィ ードバックを得て変更しやすいように特別なモジュールにしている。有効にするには --addmodules jdk.incubator.xxx の指定が必要 Preview (JEP 12): Java言語の試験機能。有効にするには --enable-preview の指定が必 要。コンパイル時には --resource XX か -source XX の指定も必要( XX はバージョン) Experimental: GCやコンパイラなどのランタイムの試験機能。 XX:+UnlockExperimentalVMOptions オプションをあわせて指定して実行する Standard: 上記のテストフェーズを通じて標準機能に昇格した機能。通例2回以上のバージョ ンアップを挟んで昇格する(例:Preview -> Second Preview -> Standard)。 2回で確実に昇格するわけではなく、コミュニティからのフィードバックなどを受けて改善が 続けられる機能もある (Java 19ではむしろ新規以外は全部3回以上継続してる)
Java 19 JEPレベルの変更(7個、新規4個(新規詐欺1個)/継続3個) JEP 422: Linux/RISC-V Port JEP 427: Pattern Matching for switch (Third Preview) JEP 405: Record Patterns (Preview) JEP 426: Vector API (Fourth Incubator) JEP 424: Foreign Function & Memory API (Preview) JEP 425: Virtual Threads (Preview) JEP 428: Structured Concurrency (Incubator) JEPレベル以外の変更 (CSRなどからピックアップ) 言語レベルの改善点 GCの改善点 セキュリティ周りの改善点
JEP 422: Linux/RISC-V Port ベクトル命令を含むRV64GVのみサポート、32-bitなどは将来的に対応予定。 ベクトル命令を含んではいるがVector APIのサポートは計画段階中。 このJEPによって一度メインラインに入ればパッチも直接[ github.com/openjdk/jdk ]] (https://github.com/openjdk/jdk)に入るので今後はそちらを見ていればOK。(今までは openjdk/riscv-port )
JEP 427: Pattern Matching for switch (Third Preview) (1/3)
Java 17からPreviewを続けているswitch文/式でpattern matching。18からの差分だけではな
く全体的に紹介。
static String message(Object o) {
return switch(o) {
case Integer i -> "Your input is number(%d)".formatted(i);
//
AND
&&
when
case String s when (s.length() >= 1) -> s;
case String s -> "empty message";
// null
(
NullPointerException
case Double d -> "Double or null";
// (Object
) default
// "incomplete"
default -> o.toString();
};
}
ガード節の表現方法が
とは意味が異なるので、 から
になった
のハンドリングもサポート ない場合は
が発生する)
の継承クラス全部列挙は現実的に不可能なので
節がないと
「すべての可能な入力値をカバーしていません」でエラー
jshell> message(1)
$2 ==> "Your input is number(1)"
JEP 427: Pattern Matching for switch (Third Preview) (2/3)
case String s when (s.length() >= 1) -> s;
case String s -> "empty message";
case null, Double d -> "Double or null";
jshell> message(1.0)
$3 ==> "Double or null"
jshell> message(null)
$4 ==> "Double or null"
jshell> message("null")
$5 ==> "null"
jshell> message("")
$6 ==> "empty message"
JEP 427: Pattern Matching for switch (Third Preview) (3/3)
勿論 null のハンドリングがなければNPEが発生する
// case null, Double d -> "Double or null";
case Double d -> "Double";
jshell> message(null)
|
java.lang.NullPointerException
|
at Objects.requireNonNull (Objects.java:233)
|
at message (#1:2)
|
at (#2:1)
例外
今回の時点で大きな変更もないので次でおそらく標準機能に導入される
JEP 405: Record Patterns (Preview) instanceof (JEP 394)のRecord版パターンマッチング record Point(int x, int y){} Before if (o instanceof Point p) { int x = p.x(); int y = p.y(); System.out.println(x+y); } After if (o instanceof Point(int x, int y)) { // x y System.out.println(x+y); } と のスコープはこの中だけ
ちょっと複雑に
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point point, Color color) {}
record ColoredSquare(ColoredPoint upperLeft, ColoredPoint lowerRight) {}
var s = new ColoredSquare(new ColoredPoint(new Point(1, 2), Color.RED),
new ColoredPoint(new Point(3, 4), Color.BLUE))
が使える
// x, y, c
s instanceof ColoredSquare(ColoredPoint(Point(var x, var y), var c), var lr)
? "UpperLeft: (%d, %d), Color: %s".formatted(x, y, c)
: "Unmatch"
$8 ==> "UpperLeft: (1, 2), Color: RED"
これより直接的
//
s instanceof ColoredSquare s
? "UpperLeft: %d, Color: %s".formatted(
s.upperLeft().point().x(), s.upperLeft().point().y(), s.upperLeft().color()
)
: "Unmatch"
JEP 424: Foreign Function & Memory API (Preview) (1/2) Foreign-Memory Access API (Java 14~) Foreign Linker API (Java 16~) Foreign Function & Memory API (Java 17~) (Scope: JDK) ↓ Foreign Function & Memory API (Scope: SE) 新規の顔をしつつ、Java 14からなので6バージョン目
JEP 424: Foreign Function & Memory API (Preview) (2/2) 改めて最終的な目標を説明すると「ヒープ外のメモリを直接扱うAPI」を提供することです。 既存APIにも同様のものはありますが、ByteBufferでDirect Bufferは2GBまででメモリの開放 はGCに依存する。sun.misc.Unsafeはクラッシュの危険性があることと最終的には廃止させ たい。JNIはJavaで完結できずCコードを書く必要があり、CとJavaで行き来する必要がある のでオーバーヘッドが発生して性能が良くないと、それぞれに欠点があります。これらを解 決しつつ新たなAPIを提供しようと言うものがこのJEPの目標です 詳しくは @YujiSoftware さんのセッションにて!
JEP 426: Vector API (Fourth Incubator) (1/2) 最近のCPUはSIMD(Single Instruction, Multiple Data, 複数データに対して同じ操作を同時に 実行することを可能にし、1つのCPUサイクルでより多くの計算を実行できる仕組み)アクセ ラーションを持つAVX2などの高度なSIMD演算をサポートしているがこれをJavaで利用する 手段がない。Hotspot JVM自体は自動ベクトル化をサポートしているが、変換可能なスカラ 演算は限られていたりと、コードに対して表現力や柔軟性がない。 これに対して、アーキテクチャに依存せず、SIMDのベクトル演算が行えるJava APIの提供を 目指す。なお、ハードウェアでサポートされていない演算を使おうとするとソフトウェア処 理になるので性能は当然落ちる(警告はされる予定)
JEP 426: Vector API (Fourth Incubator) (2/2) Java 16から開始して4回目のインキュベータ。Java 19では主に以下が実施された。 FFM (Foreign Function & Memory) API (MemorySegments)への対応 Compress/Expand 処理の追加 レーン別積分ビット演算の拡張 2017年にIntelからホワイトペーパーが公開されて約5年経過したが、もうしばらく懸かりそ う?(Future workがまだある)。 ちなみにHotSpot JVMではCPUアーキテクチャ毎の処理を @HotSpotIntrinsicCandidate などで呼び出し、OpenJDKビルド時にAD(Architecture Description)定義から自動生成され たNodeをLibraryCallで利用する等しています(他にStubGeneratorを使うパターンなどもあ る)。同様に、Vector APIもCPU命令を直接呼び出すのではなくネイティブ関数呼び出しに置 き換えて実行される(はず)。
JEP 425: Virtual Threads (Preview) (1/5) モチベーションと目標 Javaの並列処理の単位はスレッドであり、OSのシステムスレッドのラッパーとして実装 されている 新しいスレッド(Platform Thread)作成すると、OSに新しいシステムスレッドを作 成するようシステムコールが実行される 呼び出しの度に時間がかかり、各スレッドごとにいくらかのメモリを消費するなど コストが高い CPUやネットワークなどのハードウェアリソースの上限に達する遥か前にOSが一 度に対応できるスレッド数の上限に達するため、現状のスレッド実装はサーバのス ペックを使い切る前にスループットが頭打ちになるケースがある (今時は1プロセス で上限を目指すのは比較的稀だが、コストが高いのはそう)
JEP 425: Virtual Threads (Preview) (2/5) スレッドは同じCPUを共有しているので、あるスレッドがブロックして他のスレッドが 不必要に待たされる可能性がある 計算処理そのものよりも通信待ちが長くなりがち。このため、データが到着したら 何をするかを指示し、届くまで他のスレッドにCPUリソースを割り当てる非同期プ ログラミング等で対処している とはいえ、システムスレッドでの処理切り替えはオーバークオリティでCPUやメモ リを余分に使用しがちだし、自前で制御するのも煩雑 これに対して、OSではなくJVMが管理するVirtual Threadsを導入することでブロッキン グなどのコストを軽くする 一つのPlatform Thread上で多くのVirtual Threadsが実行される 従来のPlatform Threadと極力同様に扱えるように設計されている
JEP 425: Virtual Threads (Preview) (3/5) 使い方 従来の // Platform Threads jshell> var t = new Thread(() -> System.out.println("platform thread")) t ==> Thread[#28,Thread-2,5,main] jshell> t.start() platform thread はコンストラクタがない。 を にすると既存の で開始させずに得られる が得られる // Virtual Thread unstarted() // `ofVirtual()` `ofPlatform()` Platform Thread jshell> var t = Thread.ofVirtual().unstarted(() -> System.out.println("virtual thread")) t ==> VirtualThread[#29]/new jshell> t.start() virtual thread
JEP 425: Virtual Threads (Preview) (4/5) Virtual Threadsは銀の弾丸なり得るか Platform ThreadsをVirtual Threadsに置き換えることで直ちにパフォーマンスが改善するか というとそうとは限らない。 GCがある言語を採用した時点である程度のオーバーヘッドを許容しつつ開発効率性を求めて いると見なせるので、スレッド管理や実装はミドルウェアに移譲しつつ、アプリケーション の計算量や不要な同期処理の確認・見直しを実施してJMH (Java Microbenchmark Harness) やasync-profilerなどで計測してボトルネックを確認したほうが効率が良い可能性がある
JEP 425: Virtual Threads (Preview) (5/5) 注意事項 以下のような動作の非互換性や性能劣化を招くケースがある JVMTIの GetAllThreads や GetAllStackTraces はVirtual Threadを返さない agentが ThreadStart と ThreadEnd イベントを有効にしている場合、Platform Thread に限定する機能がないため性能劣化する場合がある JDK-8289317: JVM TI Changes to Support Virtual Threads java.lang.management.ThreadMXBean はPlatform Threadだけサポートしている -XX:+PreserveFramePointer はVirtual Threadの顕著な性能劣化を引き起こす 現在はまだPreviewなので実際の動作の非互換性はまだこれから Japan OpenJDK Developer's Groupで実装を見つつVirtual Threadsの発表をした @mike_neckさんがJJUG CCC 2022 Fallで発表するのでそちらも参考に
JEP 428: Structured Concurrency (Incubator) (1/6) Project Loom Virtual Threadsに連なるProject Loomの取り組みの一つ。異なるスレッドで実行される数の タスクを一つの単位として扱うことで、エラー処理やキャンセル・シャットダウン処理の効 率化を図り、リソースリークやキャンセル遅延リスクの排除、及びObservabilityの向上など を目的としている。原則は、「タスクが同時進行するサブタスクに分割された場合、それら はすべて同じタスクのコードブロックに戻る」こと。
端的に言うと、スレッドのライフサイクルを各スレッドごとに制御させることなく、(シング ルスレッドにおける)Javaのコードブロックと同じように扱いたい。例えば、Javaでメソッド AからメソッドBを呼び出した場合、メソッドBが終了してメソッドAに戻ってからメソッドA が終了する。メソッドBで例外が発生した場合は、メソッドAに伝播して例外処理が行われる (例外を伝播させた時点でメソッドBは何もせずとも終了している)。このメソッドA/Bをスレ ッドC/Dに置き換えるのと同じような感覚で使えるようにしたい。 インキュベーターなので --add-modules jdk.incubator.concurrent の指定が必要。 jshellの場合は jshell --enable-preview --add-modules jdk.incubator.concurrent
jdk.incubator.concurrent.StructuredTaskScope
を利用して実装する
実行中のスレッドいずれかが例外を出した時すべてシャットダウン
ExecutorService#invokeAll に近い
String getColoredPoint() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
//
Future<Point> point = scope.fork(() -> getPoint());
Future<Color> color = scope.fork(() -> getColor());
結果的にコードブロック全体がアトミックになる
scope.join();
scope.throwIfFailed();
//
return "Point: (%d, %d), Color: %s"
.formatted(point.resultNow().x(), point.resultNow().y(), color.resultNow());
全スレッド戻ってきたら返す
}
}
Point getPoint(){return new Point(3, 4);}
Color getColor(){return Color.RED;}
結果
jshell> getColoredPoint()
$8 ==> "Point: (3, 4), Color: RED"
例外が起きた時の結果
例外をキャッチするとすべての子スレッドをシャットダウンする
(この部分のコードは変わってない)
String getColoredPoint() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<Point> point = scope.fork(() -> getPoint());
Future<Color> color = scope.fork(() -> getColor());
を呼び出す前に
これがないと
は呼び出せない
が出力される
// join
throwIfFailed
scope.join();
//
IllegalStateException
scope.throwIfFailed();
return "Point: (%d, %d), Color: %s"
.formatted(point.resultNow().x(), point.resultNow().y(), color.resultNow());
}
}
Point getPoint() throws InterruptedException {
Thread.sleep(100_000);
return new Point(3, 4);
}
Color getColor(){throw new RuntimeException();}
jshell> getColoredPoint()
|
java.util.concurrent.ExecutionException: java.lang.RuntimeException
|
at StructuredTaskScope$ShutdownOnFailure.throwIfFailed (StructuredTaskScope.java:1125)
|
at getColoredPoint (#11:9)
|
at (#12:1)
|
: java.lang.RuntimeException
|
at getColor (#9:1)
|
at lambda$getColoredPoint$1 (#11:4)
例外
原因
がない場合
// throwIfFailed()
jshell> getColoredPoint()
|
java.lang.IllegalStateException: Task was cancelled
|
at FutureTask.resultNow (FutureTask.java:218)
|
at StructuredTaskScope$FutureImpl.resultNow (StructuredTaskScope.java:756)
|
at getColoredPoint (#13:10)
|
at (#14:1)
例外
実行中のスレッドいずれかが返ってきたら他をすべてシャットダウン
String getColoredPoint() throws ExecutionException, InterruptedException {
// ShutdownOnSuccess
try (var scope = new StructuredTaskScope.ShutdownOnSuccess<>()) {
Future<Point> point = scope.fork(() -> getPoint());
Future<Color> color = scope.fork(() -> getColor());
を使う
scope.join();
//
getColor
return "Result: " + scope.result();
先に返ってきた
}
}
の結果が出力される
Point getPoint() throws InterruptedException {
Thread.sleep(100_000);
return new Point(3, 4);
}
Color getColor(){return Color.RED;}
jshell> getColoredPoint()
$21 ==> "Result: RED"
スレッドダンプによるObservabilityの向上 JEP 425(Virtual Threads)で実はJSONのスレッドダンプ形式が追加されている。これを使う と StructuredTaskScope のスレッドを階層的にグループ化して表示される $ jcmd <pid> Thread.dump_to_file -format=json <file> 各スコープのJSONにはそのスコープでフォークされたスレッドとそのスタックトレースが含 まれている。また、各スコープのJSONはその親への参照も持っているのでスレッドダンプか らプログラムの構造を再構築することができる このスレッドダンプはJava 19から追加されたMXBeanの API( com.sun.management.HotSpotDiagnosticsMXBean#dumpThreads )を通じてJMXから 取得することも可能。(これもPreview API) Virtual Threadsを用いることでブロッキングが容易になるのはこういった取り組みも含めて という側面もある
Other noteworthy changes (JDK/Tool) JEP以外で、個人的に気になった小さい変更・改善をピックアップ JDK-8236569: -Xss はSystem Page Sizeの倍数に丸められて設定される JDK-8282823: javac はPreview methodを実装・オーバライドした場合にも警告・エ ラーを出す JDK-8282819: 性能改善(benchmark)のため Locale のコンストラクタが非推奨化され た。 Locale.of() を使いましょう JDK-8284377&JDK-8287419: HashMap / HashSet / LinkedHashMap / LinkedHashSet に容量を指定して作成するstatic methodが追加された。 HashMap.newHashMap(1) 等。これにより <> でも無検査変換警告が出ない
JDK-8277131(Virtual Threads): Thread.sleep(Duration) が追加された。 Duration.ofSeconds(1) など渡される引数の単位が明確にできるのが良い。少し関連 して、Java 20で Thread#stop がついに廃止される(JDK-8289610) JDK-8271585: JDK Flight Recorderファイルから不要なイベントデータやセンシティブ 情報を削除できる scrub 機能やAPI(jdk.jfr.consumer.RecordingFile::write)が追加された $ jfr scrub \ --include-events < --exclude-events < --output < JFR < JFR >` 必要イベント> 不要イベント> 出力 ファイル> \ 対象 ファイル や か も指定可能 のどちらかのみ \ #threads categories \ #iclude exclude
Other noteworthy changes (GC) GC関係のJEPはないが201件の改善がある (JBS) 性能改善は至る所で行われてる。 リリースごとに出しているOracleのHotSpot GCエンジニアの記事が参考になる。logicoさん が日本語訳を既に書いてくれている Virtual ThreadsとGC Virtual Threadsの導入により、Platform Threadsで扱っていたスレッドスタックを含めて Javaヒープ上にオブジェクトを導入することになった。このオブジェクトを適切に扱うため には、これらが参照する古いコンパイル済みコードのアンロード処理がかなり複雑になるた め、GCにメソッドアンロードを移譲する等の改善が行われている([JDK-8290025] Remove the Sweeper, [JDK-8284404] Too aggressive sweeping with Loom)。Virtual Threadsは Previewなので有効にしない限りは関係なし。
ZGC とくに大きな変更はなし。CDSを一部サポートした(JDK-8255495)ぐらい ZGCの世代別GC化(github)が現在進められており、リソースの大半がここに使われてい る Prallel GC、G1 GC JDK-8280705(Prallel) & JDK-8280396(G1)により、Full GCの最初のGCサイクルフェ ーズ(Marking)において、1スレッドで作業していたケースが多かったが、スレッド間の 作業配分が改善した。ユースケースによっては大きな性能改善が見込める(特にParallel GC) G1 GCの注意点 ネイティブメモリ使用量のリグレッションが発生した(約8.5%増加)(JDK-8292654) リージョンサイズx2倍> で緩和 -XX:G1RemSetArrayOfCardsEntries=< 19.0.1で対応済み(発表者はまだ未検証)
Other noteworthy changes (Security) セキュリティ関係のJEPはないが22件のsecurity-libsの改善がある(JBS) こちらもOracleのSecurity team leadの記事が参考になる。logicoさんの日本語訳 大半は最新化や危殆化したアルゴリズムの削除 TLS performance related improvements TLS Handshaking時のメモリ量削減 TLS Handshakingの処理スピード改善 11.0.17、17.0.5、8u333にバックポートされている
Java 20 0 JEP (JBS) 気になっているもの JDK-8284289: Improved way of obtaining call traces asynchronously for profiling AsyncGetCallTrace を利用した async-profiler のようなエージェントが増えているが、 Javaフレームのメソッドとバイトコードのインデックスのみが取れ、1)インライン化されて いるかどうか 2)コンパイルレベル(C1 or C2) 3) C/C++フレーム情報が取れない。 AsyncGetCallTrace2 というJava及びネイティブフレームを非同期に取得できるAPIを提供 しようというJEP。demo(github/parttimenerd/asgct2-demo), repository (jdk fork) Container-aware heap sizing for OpenJDK: G1でメモリアンコミット(uncommit)をより積極 的に行う改善など(JDK-8238687) JDK-8210708: Concurrent Marking in G1: ネイティブメモリフットプリントの減少
Head toward Java 19 KUBOTA Yuji LINE Corporation / IMF team JJUG Night Seminar 2022/Sep/30