Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Head toward Java 20 and Java 21

Head toward Java 20 and Java 21

まいどおなじみ最新のJava技術についてお届けします!

本セッションでは2023年3月にリリースされた(本Proposal投稿時点では「予定」)Java 20、そして予定通りであれば2023年9月にリリース予定である、次のLong Term SupportであるJava 21についてどのような機能や変更点が入ったのか・入る予定なのかを紹介し、「このセッションさえ聞けば今の最新状況が分かる!」を目指します。

OpenJDKコミュニティではLTSかどうかに関わらず一定品質以上を保ちつつ提供することをポリシーとしているため、LTSであるJava 21でも従来通り試験的な(IncubatorやPreview)機能が導入される予定です。

最近のJavaでは大きなプロジェクトが長く続いていることもあり、JEP(Java Enhance Proposal)レベルの変更はやや控えめですが、本セッションでは発表当日までのJava 21の開発状況を含めて、未だ止まることなく進化を続けるJavaを余すどころなくお伝えしていきます!

Yuji Kubota
LINE,Senior software engineer
OpenJDK Author, icedtea committer

※この資料は以下イベントで発表した内容です。
https://sessionize.com/api/v2/y7inyq6y/view/GridSmart

LINE Developers

June 04, 2023
Tweet

More Decks by LINE Developers

Other Decks in Technology

Transcript

  1. Head toward Java 20 and Java 21 KUBOTA Yuji LINE

    Corporation JJUG CCC 2023/06/04
  2. KUBOTA Yuji (@sugarlife) Senior Software Engineer at LINE Corporation, IMF

    team IMF team = Kafka infrastructure team エンジニアリングの究極的な挑戦の場はここにある Kafkaイン フラを全社に提供するIMFチームの仕事のやりがい: https://logmi.jp/tech/articles/325945 「膨大な量のトラフィックを扱えるLINEの環境は魅力的」 Kafkaスペシャリストが語る仕事の醍醐味: https://engineering.linecorp.com/en/interview/kafka-okada OpenJDK Author / IcedTea committer https://www.docswell.com/user/ykubota 2
  3. Java 20 and Java 21 Java/JDK 20 2023/03/21リリース non LTS:

    LTSかどうかに関係なく商用に耐えうる安定性を検証済 (ML) 7 JEP (JBS) 124 CSRs (JBS) 49 Release Note (JBS) Java/JDK 21 2023/09/19リリース予定、2023/06/08から安定化フェーズ(Rampdown)突入 LTS: Oracleは2年ごとのLTSリリース提唱 (OpenJDK ML) 16 JEP (確定していないのも含む) (JBS) 166 CSRs (JBS) 44 Release Note (JBS) 3
  4. アップデートとJEP、CSR、Release Note JEP (JDK Enhancement-Proposal) Java新機能拡張の提案 CSR (Compatibility & Specification

    Reviews) 非互換性を伴う変更のレビュー(非互換性を伴う変更は出す必要がある) Release Note 厳密な定義(※ 管理手順はめちゃくちゃ厳密に定義されている)はないが動作変更な ど、ユーザが対応・修正する必要がある変更を強調するためのもの JBS (JDK Bug System) 課題管理。JEPやCSRも各々1チケットになっている Project (OpenJDK Project) 特定の成果物を作成するための共同作業。JEPはいずれかのProjectに付属するケー スが多い(=付属しないのもある) 4
  5. JEPで導入される機能のフェーズ Incubator (JEP 11): Java APIの試験用モジュール( jdk.incubator )。標準化に向けてフィ ードバックを得て変更しやすいように特別なモジュールにしている。有効にするには --add-

    modules jdk.incubator.xxx の指定が必要 Preview (JEP 12): Java言語の試験機能。有効にするには --enable-preview の指定が必 要。コンパイル時には --release XX か --source XX の指定も必要( XX はバージョン) Experimental(試験機能): GCやコンパイラなどのランタイムの試験機能。有効にするには機 能ごとのオプション以外に、 -XX:+UnlockExperimentalVMOptions の指定が必要 Standard(標準機能): 上記のテストフェーズを通じて標準機能に昇格した機能。通例2回以上 のバージョンアップを挟んで昇格する(例:Preview -> Second Preview -> Standard)。2回で 確実に昇格するわけではなく、コミュニティからのフィードバックなどを受けて改善が続け られる機能もある 6
  6. Project Amber 開発生産性を高めるためのJava言語改善 Record Patterns JDK 20: JEP 432: Record

    Patterns (Second Preview) JDK 21: JEP 440: Record Patterns Pattern Matching for switch JDK 20: JEP 433: Pattern Matching for switch (Fourth Preview) JDK 21: JEP 441: Pattern Matching for switch String Template JDK 21: JEP 430: String Templates (Preview) Unnamed xxx JDK 21: JEP 443: Unnamed Patterns and Variables (Preview) JDK 21: JEP 445: Unnamed Classes and Instance Main Methods (Preview) 7
  7. Record Patterns 目的 instanceof パターンマッチング(JEP 394)のRecord版 if (obj instanceof String

    s) { /* 定数 s を使った処理 */} 変更点 JDK 20: JEP 432: Record Patterns (Second Preview) ジェネリクスレコードパターン(Generic Record Pattern)の型推論に対応 名前付きレコードパターン対応を削除 拡張for構文対応もあったがJDK 21で削除された JDK 21: JEP 440: Record Patterns 標準機能へ昇格 8
  8. 使用方法(JDK 21) record Point(int x, int y){} Before if (obj

    instanceof Point) { Point p = (Point) obj; int x = p.x(); int y = p.y(); System.out.println(x+y); } After if (obj instanceof Point(int x, int y)) { // x とy のスコープはこの中だけ System.out.println(x+y); } 9
  9. Generic Record Pattern record Box<T>(T t) {} static void print(Box<Box<String>>

    html) { if (html instanceof Box<Box<String>>(Box(var s))) { System.out.println("Text: " + s); } } 10
  10. 変更点 JDK 20: JEP 433: Pattern Matching for switch (Fourth

    Preview) labelにenumを利用している際、実行時に網羅されていない場合のExceptionが IncompatibleClassChangeError から MatchException に変わった IncompatibleClassChangeError はsealedクラスの移行互換性がない場合に 示される。例:sealedクラスの許容されたサブクラスのセットからクラスが削除 され、削除されたクラスの既存バイナリがロードされると出る switch labelの構文を簡素化 ジェネリクスレコードパターンの型推論に対応 JDK 21: JEP 441: Pattern Matching for switch 読み易さのために導入されていたカッコつきパターン対応の削除 Qualified enum定数をcase定数として使用可能に 標準機能へ昇格 12
  11. 使用方法(JDK 21) Before if (obj instanceof Integer) { return String.format("Your

    input is number: %d", (Integer)obj); } else if (obj instanceof Double) { : After return switch (obj) { case Integer i -> String.format("int %d", i); case Double d -> ... : } 13
  12. String message(Object o) { return switch(o) { case Integer i

    -> String.format("Your input is number: %d", i); case Double d -> throw new IllegalStateException("Invalid Double argument"); case String s when (s.length() >= 1) -> s; case String s -> "empty message"; // null のハンドリングもサポート ( ない場合はNullPointerException が発生する) case null -> "null"; // この書き方はNG になった // case null, Double d -> "Double or null"; // (Object の継承クラス全部列挙は現実的に不可能なので) default 節がないと // "Incomplete" 「すべての可能な入力値をカバーしていません」でエラー default -> o.toString(); }; } 14
  13. 正常処理とthrow exception case Integer i -> String.format("Your input is number:

    %d", i); case Double d -> throw new IllegalStateException("Invalid Double argument"); jshell> message(1) $2 ==> "Your input is number: 1" jshell> message(2.4) | 例外java.lang.IllegalStateException: Invalid Double argument | at message (#12:4) | at (#13:1) 15
  14. 条件処理 case String s when (s.length() >= 1) -> s;

    case String s -> "empty message"; jshell> message(null) $4 ==> "null" jshell> message("hoge") $5 ==> "hoge" jshell> message("") $6 ==> "empty message" 16
  15. nullハンドリングと網羅性 // null のハンドリングもサポート ( ない場合はNullPointerException が発生する) case null ->

    "null"; // (Object の継承クラス全部列挙は現実的に不可能なので) default 節がないと // "Incomplete" 「すべての可能な入力値をカバーしていません」でエラー default -> o.toString(); null のハンドリングがなければNPEが発生する jshell> message(null) | 例外java.lang.NullPointerException | at Objects.requireNonNull (Objects.java:233) | at message (#1:2) | at (#2:1) default 節がないと網羅しておらずエラー | エラー: | switch 式がすべての可能な入力値をカバーしていません | return switch(o) { | ^----------... 17
  16. Qualified enum label enum Flag { A, B } switch

    (f) { // Before case A: case B: // After // 以前は"case label must be the unqualifed name" とコンパイルエラー case Flag.A: case Flag.B: } 18
  17. JEP 430: String Templates (Preview) JDK 21で初Proposal (Implementation) 目的 //

    読みづらい String s = x + " plus " + y + " equals " + (x + y); // 細かすぎる s = new StringBuilder() .append(x) .append(" plus ") .append(y) .append(" equals ") .append(x + y) .toString(); // 引数の数や型の不一致を招きやすい s = String.format("%2$d plus %1$d equals %3$d", x, y, x + y); s = "%2$d plus %1$d equals %3$d".formatted(x, y, x + y); // 独特すぎる MessageFormat mf = new MessageFormat("{0} plus {1} equals {2}"); s = mf.format(x, y, x + y); 19
  18. 使用方法(JDK 21) JDK 21から導入された java.lang.StringTemplate.Processor を利用する var x = 10.0

    var y = 20.5 // built-in processor "STR" // \{ で始まり } で閉じる String s = STR."\{ x } plus \{ y } equals \{ x + y }" s ==> "10.0 plus 20.5 equals 30.5" Text Block s = STR.""" \{ x } plus \{ y } equals \{ x + y} """; s ==> "10.0\n plus\n20.5\n equals\n30.5\n" 20
  19. Formatter StringTemplate.Processor の1実装である java.util.FormatProcessor を利用 FormatProcessor.FMT."%2.1f\{ x } plus %2.2f\{

    y } equals %.3f\{ x + y }" $1 ==> "10.0 plus 20.50 equals 30.500" // 16 進 var x = 10 var y = 20 FormatProcessor.FMT."0x%04x\{x} + 0x%04x\{y} = 0x%04x\{x + y}" $2 ==> "0x000a + 0x0014 = 0x001e" 21
  20. Advanced usage StringTemplate.Processor<JsonObject, RuntimeException> JSON = StringTemplate.Processor.of( // 単純にJsonObject を作成して返しているがValidation

    処理を加えたりもできる (StringTemplate st) -> new JsonObject(st.interpolate()) ); String name = "Joan Smith"; String phone = "555-123-4567"; String address = "1 Maple Drive, Anytown"; // Template 形式でString を書きつつJsonObject を作成する JsonObject doc = JSON.""" { "name": "\{name}", "phone": "\{phone}", "address": "\{address}" }; """; 22
  21. JEP 443: Unnamed Patterns and Variables (Preview) JDK 21で初Proposal (Implementation)

    目的 不要な変数を明確にしたり、不要なネストを排することで読みやすさ・保守性を向上させ る。 使用方法(JDK 21) Java 9で利用禁止になった(*) Underscore _ で表現する (*): JDK-8061549 23
  22. Unnamed patterns (variable) OK if (obj instanceof Point(_, int y))

    { System.out.println(y); } if (obj instanceof Point(int _, int y)) { System.out.println(y); } switch (o) { case Point(int x, _) -> ... } NG // Type o instanceof _ o instanceof _(int x, int y) case _ 24
  23. Unnamed local variables, exception param, lambda param ブロック内のローカル変数宣言文 (JLS 14.4.2)

    var _ = hoge.inplaceMethod() (※ forなどのブロック内) try-with-resourceのリソース指定 (JLS 14.20.3) try (var _ = ScopedContext.acquire()){ // 獲得したリソースは使わない } for文のヘッダ (JLS 14.14.1, JLS 14.14.2) for (int i=0, _=sideEffect(); i<10; i++){...} for (Integer _: int){...} catchブロックのexception引数 (JLS 14.20) catch (Throwable _) { #ignore } ラムダ式のパラメータ (JLS 15.27.1) ...stream.collect(Collectors.toMap(String::toUpperCase, _ -> "NODATA")) ※ JLS(Java Language Specification)のリンクはJava 20のもの 25
  24. https://bugs.openjdk.org/browse/JDK-8309093 jshell> for (int _[] : new int[][]{new int[]{1}, new

    int[]{2}}) {} jshell> // jdk-21+24 では通ってしまう ( おそらくjdk-21+26 で直る) 26
  25. https://bugs.openjdk.org/browse/JDK-8309093 jshell> for (int _[] : new int[][]{new int[]{1}, new

    int[]{2}}) {} ^^^ これがダメ jshell> // jdk-21+24 では通ってしまう ( おそらくjdk-21+26 で直る) 27
  26. JEP 445: Unnamed Classes and Instance Main Methods (Preview) JDK

    21から導入開始 目的 クラス、パッケージ、モジュールといった概念はなしでただ実行できるようにして、プログ ラムの基本(変数、分岐、ルーチン等)から段階的に学べるようにする 28
  27. 使用方法(予定)(JDK 21) 注意: main-line(openjdk/jdk)にマージされていないためまだ確定していない (PR) Before public class Main {

    public static void main(String[] args) { System.out.println("Hello, World!"); } } After void main() { System.out.println("Hello, World!"); } Source code launcherで実行すればコンパイルすら要らない $ java --source 21 --enable-preview Main.java 29
  28. public static void main(String[] args) { System.out.println("old school"); } protected

    void main(String[] args) { System.out.println("not static"); } protected static void main() { System.out.println("no arguments"); } void main() { System.out.println("minimum"); } 30
  29. // 1 public static void main(String[] args) { System.out.println("old school");

    } // 3 protected void main(String[] args) { System.out.println("not static"); } // 2 protected static void main() { System.out.println("no arguments"); } // 4 void main() { System.out.println("minimum"); } "old school" -> "no arguments" -> "not static" -> "minimum" アクセス修飾子はprivateでなければOK 31
  30. Project Loom 軽量同時実行。軽量スレッドと同時実行性プログラミングモデル Virtual Threads JEP 436: Virtual Threads (Second

    Preview) JEP 444: Virtual Threads Scoped Values 20: JEP 429: Scoped Values (Incubator) 21: JEP 446: Scoped Values (Preview) Structured Concurrency 20: JEP 437: Structured Concurrency (Second Incubator) 21: JEP 453: Structured Concurrency (Preview) 32
  31. Virtual Threads 目的 Javaの並列処理の単位 = スレッド 従来のスレッド(Platform threads) = OSのシステムスレッドのラッパー

    Platform threadsのボトルネックやスケールのし辛さ 待ち状態 (CPU、同時アクセス、コンテキストスイッチ) 呼び出しや処理切り替えなどのコストが高い システムリソース上限よりOSのスレッド数上限に達するケースがある OSではなくJVMが管理する軽量スレッド(Virtual threads)を導入 33
  32. 変更点 JDK 20(JEP 436) JDK 19(JEP 425)で提案された内容の一部であるThreadやFutureの新メソッド追 加、ExecutorServiceをAutoCloseableに拡張変更、ThreadGroupのデグレードは 恒久化された(Previewでフィードバックを受ける対象ではなくなった) JDK

    21(JEP 444) ThreadLocal変数を常にサポート。この変数を持てないVirtual Threadsは作れなく することで既存ライブラリの移行コストを下げた 標準機能へ昇格 34
  33. 使用方法(JDK 21) // 従来のPlatform threads jshell> var t = new

    Thread(() -> System.out.println("platform threads")) t ==> Thread[#25,Thread-0,5,main] jshell> t.start() platform threads // Virtual threads はコンストラクタがない。unstarted() で開始させずに得られる // `ofVirtual()` を`ofPlatform()` にすると従来のPlatform threads が得られる jshell> var t = Thread.ofVirtual().unstarted(() -> System.out.println("virtual threads")) t ==> VirtualThread[#26]/new jshell> t.start() virtual threads 35
  34. 注意事項 (1/2) JFRやJVM TI、Java Debug InterfaceなどはVirtual threadsに対応済みであるが、一部は互換 性などの都合でPlatform threadsに限定するように修正された Thread.set{Priority,Deamon}(...)

    はVirtual Threadsには効果がない Virtual threadsはThreadGroupのアクティブメンバーではない。 Thread.getThreadGroup() を呼び出してもダミーの空グループが返る Thread.getAllStackTraces() は全てのスレッドではなくPlatform threadsのみ返す com.sun.management.HotSpotDiagnosticMXBean#dumpThreads が新しく追加 され virtual threadsのダンプも出すようになった 36
  35. 注意事項 (2/2) JVM TIの GetAllThreads や GetAllStackTraces はVirtual Threadを返さない JVM

    TI agentが ThreadStart と ThreadEnd イベントを有効にしている場合、 Platform Threadsに限定する機能がないため性能劣化する場合がある java.lang.management.ThreadMXBean はPlatform Threadsだけサポートしている findDeadlockedThreads も当然Platform Threadsのみ Virtual threadsはSecurity Managerが有効だとpermissionがない -XX:+PreserveFramePointer はVirtual Threadsの顕著な性能劣化を引き起こす 37
  36. 変更点 JDK 20: JEP 429: Scoped Values (Incubator) 提案開始。Incubator( jdk.incubator.concurrent

    )。 JDK 21: JEP 446: Scoped Values (Preview) まだ確定ではない(Proposed to target) Preview ( java.lang ) へ ScopeValue.where が runWhere , callWhere , getWhere と操作が明確なメソッ ド名に変更 JDK 20では ScopeValue.where(...).run(...) のような書き方だった 40
  37. 使用方法(予定)(JDK 21) 注意: main-line(openjdk/jdk)にマージされていないためまだ確定していない (PR) private final static ScopedValue<User> USER

    = ScopedValue.newInstance(); // 引数は ScopedValue のkey, value, Runnable // 渡したRunnable が完了したら即座に解放される ScopedValue.runWhere(USER, new User("duke"), () -> doSomething()); // Before void doSomething(User user){ ... // User に対する処理 } // After void doSomething() { User user = USER.get(); // duke ... // User に対する処理 } 41
  38. Rebinding void doSomething() { User user = USER.get(); // duke

    ... // User に対する処理 // 再バインド (userService から最新のユーザ情報を入手してバインドする例) ScopedValue.runWhere(USER, userService.get(), () -> doAnother() ); // doAnother() が完了すると元の"duke" ユーザにバインドされ直される } void doAnother() { User user = USER.get(); // userService.get() で得たユーザ ... } 42
  39. Inheriting Scoped Values // StructuredTaskScope によって生成された子スレッドは、親スレッドのスコープ値にアクセス可能 // ThreadLocal を使用すると、その値が各子スレッドにコピーされる(= メモリがどーん)

    ScopedValue.runWhere(USER, new User("duke"), () -> { try (var scope = new StructuredTaskScope<User>()) { scope.fork(() -> childTask()); ... } }); String childTask() { User user = USER.get(); // duke ... } 43
  40. 変更点 JDK 20: JEP 437: Structured Concurrency (Second Incubator) Scoped

    Values対応 (先ほど説明したInheriting Scoped Values) JDK 21: JEP 453: Structured Concurrency (Preview) まだ確定ではない(Proposed to target) Incubator( jdk.incubator.concurrent )からPreview ( java.util.concurrent ) へ StructuredTaskScope::fork が Future ではなく StructuredTaskScope.Subtask を返すように変更 45
  41. 使用方法(予定)(JDK 21) 注意: main-line(openjdk/jdk)にマージされていないためまだ確定していない (PR)。前述の Scoped Valueと同じPR // サブタスクで実行させる処理のリスト List<Callable<String>>

    tasks = ... try (var scope = new StructuredTaskScope<String>()) { // scope.fork() によりサブタスクを実行( フォーク) List<Subtask<String>> subtasks = tasks.stream().map(scope::fork).toList(); // サブタスクの完了を待機 scope.join(); // 成功サブタスクと失敗サブタスクを別セットに分割 Map<Boolean, Set<Subtask<String>>> map = subtasks.stream()  .collect(Collectors.partitioningBy( h -> h.state() == Subtask.State.SUCCESS, Collectors.toSet())); } // 全リソースクローズ 46
  42. 実行中のスレッドいずれかが返ってきたら他をすべてシャットダウン ShutdownOnSuccess を利用する try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {

    List<Subtask<String>> subtasks = tasks.stream().map(scope::fork).toList(); // いずれか一つのサブタスクが成功(Subtask.State#SUCCESS) するか、 // すべて失敗(Subtask.State#FAILED) するまで待つ scope.join(); try { // 最初に成功したサブタスクの結果を取得 String result = scope.result(); ... // 成功したタスクの結果を利用した処理 } catch (ExecutionException e) { // すべて失敗した場合はExecutionException が投げられる throw new CustomException(e); } } 47
  43. 実行中のスレッドいずれかが例外を出したらすべてシャットダウン ShutdownOnFailure を利用する try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {

    // 結果が欲しいだけなのでSupplier を使う List<Supplier<String>> suppliers = tasks.stream().map(scope::fork).toList(); // いずれか一つのサブタスクが失敗するか、すべて成功するか、10 秒経過するまで待つ // (10 秒過ぎたらTimeoutException) Instant deadline = LocalDateTime.now().plusSeconds(10).toInstant(ZoneOffset.UTC); scope.joinUntil(deadline); // 最初に失敗したサブタスクの例外を補足して伝播、終了 scope.throwIfFailed(e -> new CustomException(e)); // 全てのサブタスクが成功した場合の処理 ( サブタスクの結果(String) を並べてるだけ) String result = suppliers.stream() .map(Supplier::get) .collect(Collectors.joining(", ")); } 48
  44. スレッドダンプによるObservabilityの向上 Java 19からVirtual ThreadのJEP(JEP 425)でJSONのスレッドダンプ形式が追加された。こ れを使うと StructuredTaskScope のスレッドを階層的にグループ化して表示可能 $ jcmd

    <pid> Thread.dump_to_file -format=json <file> 各スコープのJSONにはそのスコープでフォークされたスレッドとそのスタックトレースが含 まれている。また、各スコープのJSONはその親への参照も持っているのでスレッドダンプか らプログラムの構造を再構築することができる これはJMX( com.sun.management.HotSpotDiagnosticsMXBean#dumpThreads )から取得 することも可能(Java 21から標準機能) Virtual Threadsを用いることでブロッキングが容易になるのはこういった取り組みも含めて という側面もある 49
  45. 50

  46. Project Panama JVMと外部(Java以外の)ライブラリとのインタラクションを改善、強化するプロジェクト Vector API 20: JEP 438: Vector API

    (Fifth Incubator) 21: JEP 448: Vector API (Sixth Incubator) Foreign Function & Memory API 20: JEP 434: Foreign Function & Memory API (Second Preview) 21: JEP 442: Foreign Function & Memory API (Third Preview) 52
  47. Vector API 目的 SIMD(Single Instruction, Multiple Data, 複数データに対して同じ操作を同時に実行すること を可能にし、1つのCPUサイクルでより多くの計算を実行できる仕組み)演算をサポートする ベクトル演算を実行するためのJava

    API ベクトル演算を最適なハードウェアレジスタとベクトル命令にコンパイルするC2コンパ イラ 計算爆発しないように様々な演算(単項演算、二項演算、変換演算等)に対応する汎 用化された内部演算を定義 53
  48. 変更点 JDK 20: JEP 438: Vector API (Fifth Incubator) Bug

    fixと性能改善のみ JDK 21: JEP 448: Vector API (Sixth Incubator) Buf fixと性能改善のみ 54
  49. 使用方法(JDK 21) // c = (a*a + b*b) void vectorComputation(float[]

    a, float[] b, float[] c) { for (int i = 0; i < a.length; i += SPECIES.length()) { var m = SPECIES.indexInRange(i, a.length); var va = FloatVector.fromArray(SPECIES, a, i, m); var vb = FloatVector.fromArray(SPECIES, b, i, m); var vc = va.mul(va).add(vb.mul(vb)); vc.intoArray(c, i, m); } } 56
  50. Foreign Function & Memory API 目的 以下の既存APIの欠点を克服した「ヒープ外のメモリを直接扱うAPI」を提供する ByteBuffer でDirect Bufferは2GBまででメモリの開放はGCに依存する

    sun.misc.Unsafe はクラッシュの危険性があることと最終的には廃止させたい JNIはJavaで完結できずCコードを書く必要があり、CとJavaで行き来する必要があるの でオーバーヘッドが発生して性能が良くない 57
  51. 変更点 かれこれJava 14(JEP 370)からだが(※ Foreign-Memory Access APIからカウント)いま だにbreaking changeが多い JDK

    20: JEP 434: Foreign Function & Memory API (Second Preview) MemorySegment と MemoryAddress の使い勝手(abstraction)を統一 sealed MemoryLayout をパターンマッチ(switch)で使いやすくした MemorySession を Arena と SegmentScope に分割 JDK 21: JEP 442: Foreign Function & Memory API (Third Preview) Native Segmentのライフタイム管理を Arena インターフェースに一元化 アドレスレイアウトを日参照にする新要素を追加 短命でJavaサイドに呼ばれない関数の呼び出しを最適化するリンカオプションを追 加 libffi をベースとしたfallback native linkerを提供 VaList クラスを削除 58
  52. 使用方法(JDK 21) 関連クラスは java.lang.foreign packageにある //strlen で文字列長を出す // ライブラリ準備 (Linker)

    Linker linker = Linker.nativeLinker(); SymbolLookup stdlib = linker.defaultLookup(); java.lang.invoke.MethodHandle strlen = linker.downcallHandle( stdlib.find("strlen").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS) ); // メモリ準備(malloc) try (Arena arena = Arena.ofConfined()) { MemorySegment cString = arena.allocateUtf8String("Hello, World"); long len = (long)strlen.invokeExact(cString); System.out.println(len); // 12 } // 解放 (free) 詳細な内容はJEP Ownerのポストへ: https://inside.java/u/MaurizioCimadamore/ 59
  53. No Project JDK 21: JEP 404: Generational Shenandoah (Experimental) JDK

    21: JEP 439: Generational ZGC JDK 21: JEP 431: Sequenced Collections JDK 21: JEP 451: Prepare to Disallow the Dynamic Loading of Agents JDK 21: JEP 449: Deprecate the Windows 32-bit x86 Port for Removal JDK 21: JEP 452: Key Encapsulation Mechanism API 60
  54. JDK 21: JEP 404: Generational Shenandoah (Experimental) https://github.com/openjdk/jdk/pull/14185 目的 世代を分けてよりサイクルを回すことで回収効率が改善して以下が見込める

    (持続的な)メモリ使用量(memory overhead)、CPU・電源使用量の削減 allocation failureによるSTWを要求する余分なGC発生リスクの低減 Degenerated GC: allocation failureとなった場合にSTWを発生させて回収する Full GC: Degeneratedでも十分なメモリが確保できなかった場合等の最終手段 現在はx64とAArch64をサポート。将来的にデフォルトにする方向 使用方法(JDK 21) -XX:ShenandoahGCMode=generational 61
  55. JDK 21: JEP 439: Generational ZGC https://github.com/openjdk/jdk/pull/13771 目的 世代別によるメリットはShenandoahと原則一緒。こちらも最終的にメンテナンスコスト削減 のため置き換え予定

    JDK 17からZGCでは UseDynamicNumberOfGCThreads を尊重して並行GCスレッド数を調整 してCPU使用量を抑えることができるが、より抑えられる可能性がある 使用方法(JDK 21) -XX:+ZGenerational 62
  56. JDK 21: JEP 431: Sequenced Collections 目的 順序(encounter order)を定義するシーケンス型がない 最初|最後の要素取得などの操作がコレクションごとに独自実装で一貫性がない

    List / Deque -> 順序性はあるがスーパータイプは Collection であり未定義 Set -> 定義がない。一部サブクラスにはある(例: SortedSet / LinkedHashSet ) LinkedHashSet を Collections::unmodifiableSet でラップすると順序の定義 が失われる ということで、順序付き要素のシーケンスを表すインターフェースを追加 63
  57. 逆順で反復処理する際の書き方 Before // List for (var it = list.listIterator(list.size()); it.hasPrevious();)

    { var e = it.previous(); ... } // Deque for (var it = deque.descendingIterator(); it.hasNext();) { var e = it.next(); ... } // Set navigableSet.descendingSet().stream()... After everyCollection.reversed().stream()... 64
  58. 65

  59. 67

  60. SequencedSet 重複を含まない SequencedCollection の Set SortedSet のように相対比較で要素を配置するコレクションは、 addFirst(E) や addLast(E)

    のような明示的な配置操作ができないため、 UnsupportedOperationException をスロー interface SequencedSet<E> extends Set<E>, SequencedCollection<E> { SequencedSet<E> reversed(); // covariant override (+ Set<E>) } 68
  61. 69

  62. SequencedMap エントリーが定義された順序を持つ Map メソッド的には NavigableMap に reversed() , sequenced{KeySet,Values,EntrySet} ()

    , put{First,Last}(K, V) が追加された形 put{First,Last}(K, V) は、 LinkedHashMap ではエントリーが既に存在する場合そのエ ントリーを再配置するが、 SortedMap などでは UnsupportedOperationException をスロ ーする 70
  63. interface SequencedMap<K,V> extends Map<K,V> { // new methods SequencedMap<K,V> reversed();

    SequencedSet<K> sequencedKeySet(); SequencedCollection<V> sequencedValues(); SequencedSet<Entry<K,V>> sequencedEntrySet(); V putFirst(K, V); V putLast(K, V); // methods promoted from NavigableMap Entry<K, V> firstEntry(); Entry<K, V> lastEntry(); Entry<K, V> pollFirstEntry(); Entry<K, V> pollLastEntry(); } 71
  64. JDK 21: JEP 451: Prepare to Disallow the Dynamic Loading

    of Agents 未確定(Proposed to target) Integrated (2023-06-02) 目的 Java agent( java.lang.instrument )やJVM TI agentはAttach APIを利用することで動的に 実行中のプロセスにアタッチすることができる。HeapStatsの例(AgentAttacher.java) これはJVMプロセス起動後からクラスの挙動を変えることが可能であり、起動時にオプショ ン( -javaagent や -agentlib )で指定する場合は意図通りであると保証できるが、動的ロー ドはその保証がない。このため、動的ロードの仕組みやAttach APIは消さないが、デフォルト で禁止にして禁止していないことを明示させる形式に変更する 73
  65. JDK 21: JEP 449: Deprecate the Windows 32-bit x86 Port

    for Removal 目的 Windows 32-bit x86 portの廃止 1)Virtual Threadsの対応がつらい 2)32bit対応している最後のOSであるWindows 10が 2025/10月にEoL 使用方法 対処方法 なし(自分でメンテナンスしてビルドする) 75
  66. JDK 21: JEP 452: Key Encapsulation Mechanism API 未確定(Proposed to

    target) Completed (2023-06-02) 目的 量子攻撃などから守るため、共通鍵暗号で利用する秘密鍵を配送するための手段として鍵カ プセル化メカニズムKEM(Key Encapsulation Mechanisms)のAPIを提供する 使用方法(予定)(JDK 21) 注意: main-line(openjdk/jdk)にマージされていないためまだ確定していない (PR) 発表4日前 にマージされた(commit)。jdk-21+25から有効 76
  67. 共通鍵暗号で鍵カプセル化 // Sender side KEM kemS = KEM.getInstance("ABC-KEM"); PublicKey pkR

    = retrieveKey(); ABCKEMParameterSpec specS = new ABCKEMParameterSpec(...); KEM.Encapsulator e = kemS.newEncapsulator(pkR, null, specS); KEM.Encapsulated enc = e.encapsulate(); SecretKey secS = enc.key(); sendBytes(enc.encapsulation()); sendBytes(enc.params()); 78
  68. 鍵非カプセル化(decapsulation) // Receiver side byte[] em = receiveBytes(); byte[] params

    = receiveBytes(); KEM kemR = KEM.getInstance("ABC-KEM"); AlgorithmParameters algParams = AlgorithmParameters.getInstance("ABC-KEM"); algParams.init(params); ABCKEMParameterSpec specR = algParams.getParameterSpec(ABCKEMParameterSpec.class); KEM.Decapsulator d = kemR.newDecapsulator(kp.getPrivate(), specR); SecretKey secR = d.decapsulate(em); 79
  69. Other noteworthy changes Tool (21) JDK-8306704: JFR: Summary views jfr

    view pinned-threads file.jfr (22) JDK-8308230: JFR: Query views jfr query "select * from GarbageCollection" file.jfr (?) JDK-8284289: JEP 435: Asynchronous Stack Trace VM API Java及びネイティブの両フレームを含むスタックトレースを非同期に取得でき るAPI AsyncGetStackTrace の提供 80
  70. GC (G1) (20) JDK-8297247: Add GarbageCollectorMXBean for Remark and Cleanup

    pause time in G1 (20) JDK-8210708: Use single mark bitmap in G1 マークビットマップの一つを削除してネイティブメモリフットプリントを削減 (ヒープサイズの約1.5%) (20) JDK-8137022: Concurrent refinement thread adjustment and (de-)activation suboptimal Refinement threadの最適化(Remembered Set関連処理) 関連オプション G1ConcRefinement.* , G1UseAdaptiveConcRefinement が廃止された (21) JDK-8225409: G1: Remove the Hot Card Cache Refinementに適したデータ構造だったが、もはや不要なので削除。ネイティブ メモリ削減 (21) JDK-8297639: Remove preventive GCs in G1 Evacuation failedに殆ど寄与しない状況になったので削除 81
  71. Security (20) JDK-8155246: Throw error if default java.security file is

    missing 指定したファイルがなければ実行時に InternalError が発生する (20) JDK-8256660: Disable DTLS 1.0 DTLS 1.0がデフォルトで無効化。DTLS 1.2を使いましょう (20) JDK-8279164: Disable TLS_ECDH_* cipher suites ECDH暗号スイートがデフォルトで無効化 82
  72. Head toward Java 20 and Java 21 [End of slide]

    KUBOTA Yuji LINE Corporation JJUG CCC 2023/06/04 83
  73. Java 20 JEP 429: Scoped Values (Incubator) JEP 432: Record

    Patterns (Second Preview) JEP 433: Pattern Matching for switch (Fourth Preview) JEP 434: Foreign Function & Memory API (Second Preview) JEP 436: Virtual Threads (Second Preview) JEP 437: Structured Concurrency (Second Incubator) JEP 438: Vector API (Fifth Incubator) 85
  74. Java 21 JEP 404: Generational Shenandoah (Experimental) JEP 430: String

    Templates (Preview) JEP 431: Sequenced Collections JEP 439: Generational ZGC JEP 440: Record Patterns JEP 441: Pattern Matching for switch JEP 442: Foreign Function & Memory API (Third Preview) JEP 443: Unnamed Patterns and Variables (Preview) JEP 444: Virtual Threads JEP 445: Unnamed Classes and Instance Main Methods (Preview) JEP 448: Vector API (Sixth Incubator) JEP 449: Deprecate the Windows 32-bit x86 Port for Removal 86
  75. Java 21 (未確定 / PROPOSED to Target) JEP 446: Scoped

    Values (Preview) JEP 451: Prepare to Disallow the Dynamic Loading of Agents JEP 452: Key Encapsulation Mechanism API JEP 453: Structured Concurrency (Preview) 87
  76. JDK-8284289: JEP 435: Asynchronous Stack Trace VM API AsyncGetCallTrace を利用した

    async-profiler のようなエージェントが増えている が、Javaフレームのメソッドとバイトコードのインデックスのみが取れ、1)インライン 化されているかどうか 2)コンパイルレベル(C1 or C2) 3) C/C++フレーム情報が取れな い。 AsyncGetStackTrace というJava及びネイティブフレームを非同期に取得できる APIを提供しようというJEP Container-aware heap sizing for OpenJDK: G1でメモリアンコミット(uncommit)をより 積極的に行う改善など(JDK-8238687) 88
  77. { "threadDump": { "processId": "16460", "time": "2023-06-03T12:41:45.238519600Z", "runtimeVersion": "21-ea+24-2086", "threadContainers":

    [ { "container": "<root>", "parent": null, "owner": null, "threads": [ { "tid": "1", "name": "main", "stack": [ "java.base\/java.lang.Object.wait0(Native Method)", "java.base\/java.lang.Object.wait(Object.java:366)", "java.base\/java.lang.Object.wait(Object.java:339)", "jdk.jshell\/jdk.jshell.execution.impl.PipeInputStream.read(PipeInputStream.java:53)", "java.base\/java.io.ObjectInputStream$PeekInputStream.peek(ObjectInputStream.java:2893)", "java.base\/java.io.ObjectInputStream$BlockDataInputStream.readBlockHeader(ObjectInputStream.java:3107)", "java.base\/java.io.ObjectInputStream$BlockDataInputStream.refill(ObjectInputStream.java:3177)", "java.base\/java.io.ObjectInputStream$BlockDataInputStream.read(ObjectInputStream.java:3336)", "java.base\/java.io.ObjectInputStream$BlockDataInputStream.read(ObjectInputStream.java:3258)", "java.base\/java.io.DataInputStream.readFully(DataInputStream.java:208)", "java.base\/java.io.DataInputStream.readInt(DataInputStream.java:385)", "java.base\/java.io.ObjectInputStream$BlockDataInputStream.readInt(ObjectInputStream.java:3454)", "java.base\/java.io.ObjectInputStream.readInt(ObjectInputStream.java:1160)", "jdk.jshell\/jdk.jshell.execution.ExecutionControlForwarder.processCommand(ExecutionControlForwarder.java:126)", "jdk.jshell\/jdk.jshell.execution.ExecutionControlForwarder.commandLoop(ExecutionControlForwarder.java:266)", "jdk.jshell\/jdk.jshell.execution.Util.forwardExecutionControl(Util.java:78)", "jdk.jshell\/jdk.jshell.execution.Util.forwardExecutionControlAndIO(Util.java:148)", "jdk.jshell\/jdk.jshell.execution.RemoteExecutionControl.main(RemoteExecutionControl.java:74)" ] }, { "tid": "8", "name": "Reference Handler", "stack": [ "java.base\/java.lang.ref.Reference.waitForReferencePendingList(Native Method)", "java.base\/java.lang.ref.Reference.processPendingReferences(Reference.java:246)", "java.base\/java.lang.ref.Reference$ReferenceHandler.run(Reference.java:208)" ] }, { "tid": "9", "name": "Finalizer", "stack": [ "java.base\/java.lang.Object.wait0(Native Method)", "java.base\/java.lang.Object.wait(Object.java:366)", "java.base\/java.lang.Object.wait(Object.java:339)", "java.base\/java.lang.ref.NativeReferenceQueue.await(NativeReferenceQueue.java:48)", "java.base\/java.lang.ref.ReferenceQueue.remove0(ReferenceQueue.java:158)", "java.base\/java.lang.ref.NativeReferenceQueue.remove(NativeReferenceQueue.java:89)", "java.base\/java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:173)" ] }, { "tid": "10", "name": "Signal Dispatcher", "stack": [ ] }, { "tid": "11", "name": "Attach Listener", "stack": [ "java.base\/java.lang.Thread.getStackTrace(Thread.java:2450)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadToJson(ThreadDumper.java:262)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToJson(ThreadDumper.java:237)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToJson(ThreadDumper.java:201)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToFile(ThreadDumper.java:115)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToJson(ThreadDumper.java:84)" ] }, { "tid": "21", "name": "Notification Thread", "stack": [ ] }, { "tid": "23", "name": "Common-Cleaner", "stack": [ "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", "java.base\/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:269)", "java.base\/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1847)", "java.base\/java.lang.ref.ReferenceQueue.await(ReferenceQueue.java:71)", "java.base\/java.lang.ref.ReferenceQueue.remove0(ReferenceQueue.java:143)", "java.base\/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:218)", "java.base\/jdk.internal.ref.CleanerImpl.run(CleanerImpl.java:140)", "java.base\/java.lang.Thread.run(Thread.java:1583)", "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)" ] }, { "tid": "24", "name": "output reader", "stack": [ "java.base\/sun.nio.ch.SocketDispatcher.read0(Native Method)", "java.base\/sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:46)", "java.base\/sun.nio.ch.NioSocketImpl.tryRead(NioSocketImpl.java:256)", "java.base\/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:307)", "java.base\/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:346)", "java.base\/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:796)", "java.base\/java.net.Socket$SocketInputStream.read(Socket.java:1099)", "java.base\/java.net.Socket$SocketInputStream.read(Socket.java:1093)", "java.base\/java.io.FilterInputStream.read(FilterInputStream.java:71)", "jdk.jshell\/jdk.jshell.execution.DemultiplexInput.run(DemultiplexInput.java:60)" ] }, { "tid": "25", "name": "Read-Poller", "stack": [ "java.base\/sun.nio.ch.WEPoll.wait(Native Method)", "java.base\/sun.nio.ch.WEPollPoller.poll(WEPollPoller.java:65)", "java.base\/sun.nio.ch.Poller.poll(Poller.java:363)", "java.base\/sun.nio.ch.Poller.pollLoop(Poller.java:270)", "java.base\/java.lang.Thread.run(Thread.java:1583)", "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)" ] }, { "tid": "26", "name": "Read-Updater", "stack": [ "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", "java.base\/java.util.concurrent.locks.LockSupport.park(LockSupport.java:371)", "java.base\/java.util.concurrent.LinkedTransferQueue$Node.block(LinkedTransferQueue.java:470)", "java.base\/java.util.concurrent.ForkJoinPool.unmanagedBlock(ForkJoinPool.java:3780)", "java.base\/java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3725)", "java.base\/java.util.concurrent.LinkedTransferQueue.awaitMatch(LinkedTransferQueue.java:669)", "java.base\/java.util.concurrent.LinkedTransferQueue.xfer(LinkedTransferQueue.java:616)", "java.base\/java.util.concurrent.LinkedTransferQueue.take(LinkedTransferQueue.java:1286)", "java.base\/sun.nio.ch.Poller.updateLoop(Poller.java:286)", "java.base\/java.lang.Thread.run(Thread.java:1583)", "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)" ] }, { "tid": "27", "name": "Write-Poller", "stack": [ "java.base\/sun.nio.ch.WEPoll.wait(Native Method)", "java.base\/sun.nio.ch.WEPollPoller.poll(WEPollPoller.java:65)", "java.base\/sun.nio.ch.Poller.poll(Poller.java:363)", "java.base\/sun.nio.ch.Poller.pollLoop(Poller.java:270)", "java.base\/java.lang.Thread.run(Thread.java:1583)", "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)" ] }, { "tid": "28", "name": "Write-Updater", "stack": [ "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", "java.base\/java.util.concurrent.locks.LockSupport.park(LockSupport.java:371)", "java.base\/java.util.concurrent.LinkedTransferQueue$Node.block(LinkedTransferQueue.java:470)", "java.base\/java.util.concurrent.ForkJoinPool.unmanagedBlock(ForkJoinPool.java:3780)", "java.base\/java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3725)", "java.base\/java.util.concurrent.LinkedTransferQueue.awaitMatch(LinkedTransferQueue.java:669)", "java.base\/java.util.concurrent.LinkedTransferQueue.xfer(LinkedTransferQueue.java:616)", "java.base\/java.util.concurrent.LinkedTransferQueue.take(LinkedTransferQueue.java:1286)", "java.base\/sun.nio.ch.Poller.updateLoop(Poller.java:286)", "java.base\/java.lang.Thread.run(Thread.java:1583)", "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)" ] } ], "threadCount": "12" }, { "container": "ForkJoinPool.commonPool\/jdk.internal.vm.SharedThreadContainer@88950e6", "parent": "<root>", "owner": null, "threads": [ ], "threadCount": "0" }, { "container": "java.util.concurrent.ThreadPoolExecutor@485dae5d\/jdk.internal.vm.SharedThreadContainer@75907f2", "parent": "<root>", "owner": null, "threads": [ ], "threadCount": "0" } ] } } 89
  78. 90