Oracle Developers Live Java 視聴ログ

developer.oracle.com

Java 15 のリリースに合わせて開催されたオンラインイベントのセッション動画が公開されたので面白かったところのメモ。



Java Language Futures—Mid 2020 Edition (35:32)

JDK 15 で導入されたフィーチャーの紹介。

  • Records (JEP384)
    • ボイラープレートコードを省略すること自体は目的じゃない
    • 他の言語でいうタプルのような存在
  • Pattern matching for instanceof (JEP375)
    • instanceof でガードしていたキャストは撲滅できる
      • 論理積演算子 && でつなげるから真偽値を返す?
      • 式なのか文なのか…
  • Sealed Classes (JEP360)
    • 特筆することがない…
  • Text Blocks (JEP379)
    • 紹介されなかった

Java 15新機能まとめ - Qiitaを読めばいいことがわかった。

ZGC: The Next Generation Low-Latency Garbage Collector (39:26)

発音:ズィージーシー

今利用できる GC アルゴリズムと特徴

  • ZGC はレイテンシ(GCによる停止時間)の削減を目指してる
  • スループット(GCによる処理数)を大きくするアルゴリズムはヒープメモリの使用効率を向上しようとしてる
GC Optimized for
Serial Memory Footprint
Parallel Throughput
G1 Throughput/Latency Balance
ZGC Low Latency

ZGC の特徴

  • リージョンベースで並行処理
  • NUMA対応でトレーシング可能
  • ロードバリアによるコンパクション
  • 単一世代で色ポインタを利用する
    • ヤング・オールドみたいな世代はない
  • 停止時間はヒープサイズやライブセットサイズに依存しない(ルートセットサイズに依存する)
  • クラスアンロードもできる

ZGC の振る舞い

  • マークスタートフェーズ
    • 10ms 以下
    • 対象オブジェクトにマークする
    • マッピングを修正する
  • マークエンドフェーズ
    • 10ms 以下
    • マークしたオブジェクトをリアロケートするための準備
  • リアロケートフェーズ
    • 10ms 以下
    • 準備したオブジェクトをリアロケートする

色ポインタ

  • オブジェクトポインタの構造
    • 未使用ビットとオブジェクトアドレスの間の 4 bit をメタデータとして利用する
    • マーク済み、とか、リアロケート対象、とか
p = 未使用 (16 bit) +
    色ポインタ (4 bit) +
    オブジェクトアドレス(44 bit, 16 TB に相当する)

ロードバリア

  • ヒープのオブジェクトを参照するときに JIT がコード片を挿入する
  • ロードしたタイミングで色ポインタをチェックする
  • チェックして問題があれば修復する

ZGC の性能

ZGC の未来

  • GC停止時間のさらなる削減 - 1 ms 以下にしたい
  • GC停止時間がルートセットサイズに依存しないようにしたい
    • 今のところスレッドスタック処理に未対応なため (JEP 376 で議論してる)
  • 世代別 GC

Keeping Your Java Applications Secure: Cryptographic Improvements and Best Practices (37:09)

Cryptographic Improvements

  • 安全なアルゴリズムの提供
    • JDK のリリースごとにより優れたアルゴリズムが追加されている
    • バックポートされているから昔のバージョンでもアップデートしていく必要がある
  • 安全でないアルゴリズムの使用を制限
  • 関連する警告やイベントを出力するようになっている
    • keytooljarsigner の出力に気を付けよう
    • JFR で観測できるイベントが増えている
      • jdk.SecurityPropertyModification
      • jdk.TLSHandshake
      • jdk.X509Validation
      • jdk.X509Certificate

Secure Programming Best Practices

Secure Coding Guidelines for Java SE の紹介。

コーディングガイドは10章構成。

  • 1-3章は基礎
    • DoS
    • 機密情報の保護
    • 注入攻撃
  • 4-10章はコードと権限に関連する

Java に限定した話じゃないので注意が必要。

要するに、プロセスの外部から入力されるデータは信用できないので、言語機能を公開するべきではない、みたいな感じ。

自分でコーディングする場合以外に、フレームワークやソリューションを選定するときも注意したほうがいいと思う。

This Ain’t Your Parent’s Java (40:45)

個人的に心の中で Java 芸人と呼んでいるベンカット・サブラマニアンのマシンガントーク (貴重な Emacsen でもある)。

Project Loom: Modern Scalable Concurrency for the Java Platform (38:34)

並列性の目的はスループット

  • スループットはリトルの法則 L=λW に支配されている
    • λ - 並列度 (制御できる)
    • W - 平均レイテンシ (外部サービスへのアクセスなど、制御できない部分が混在する)
    • L - 並列性の度合

現在の Java スレッド

  • OS スレッドのラッパー
    • タスクスイッチにカーネルモードへの切り替えが伴う
    • OS のスケジューリングに依存
    • キャッシュの局所性が悪い
  • スレッドごとの消費リソースが大きい
  • スレッドプールを使えば再利用できるけど別の問題が生じる
    • トランザクションが完了したら戻すモデル (request thread 的な話)
      • スレッドローカルがリークする
      • キャンセル処理が複雑化する
    • 待ち状態になったら戻すモデル (nio 的な話)
      • API に互換性がない
      • コンテキストを維持できない
  • 結局のところ同期処理と非同期処理のトレードオフになる
    • 人間にやさしい vs マシンにやさしい
    • 簡単 vs 複雑
  • いいとこどりしたい
    • 前方互換性を保ちつつ( 既存の資産を守りつつ ) 新しい機能を導入したい

そこで Java スレッドの再設計

  • Thread.currentThead() はあちこちで使われてるので注意が必要
  • Java 5 以降は Executor/Future を利用するようになってるから直接的に Thread API を使う場面は減っている
  • Thread API を大掃除すればかなりフットプリントを削減できる

新しい Java スレッド = Virtual Threads

  • スタックのサイズを可変にする
    • メタデータに 2kB 使っていたのを 200-300B にする
    • スタックに 1MB 使っていたのを可変にする
  • ユーザーモードコンテキストスイッチできるようにする
    • 長くて 10μs から 200ns 以内に短縮する
  • スケジューラーを交換可能にする

利用者にとってのメリット

構造化並列性という概念の導入

  • 構造化とは ?
    • ランタイムの振る舞いをコードに反映すること
    • libdill(C)や Trio(Python) で採用されている

Executor API の改善

  • 1 ThreadExecutor は Auto-Closeable になり、すべてのタスクが完了するまで待機する
    • スレッドプールにタスクを submit する API は変わらない
  • 2 指定した期間を経過したらタスクを実行してるスレッドを interrupt する
  • 3 join や wait や cancel を簡単に扱えるようになるはず

コード例。

// 1
ThreadFactory factory = Thread.builder().virtual().factory();
try (var executor = Executors.newThreadFactory(factory)) {
    executor.submit(task1);
    executor.submit(task2);
}

// 2
ThreadFactory factory = Thread.builder().virtual().factory();
try (var executor = Executors.newThreadFactory(factory)
                             .withDeadline(Instant.now().plusSeconds(30))) {
    executor.submit(task1);
    executor.submit(task2);
}

// 3
try (var executor = Executors.newVirtualThreadExecutor()) {
    String first = executor.invokeAny(List.of(
        () -> "a",
        () -> { throw new IOException("too lazy for work"); },
        () -> "b"
    ));
    System.out.println("one result: " + first);
} catch (ExecutionException e) {
    e.printStackTrace();
}

Collections Refueled (30:16)

JDK 9 以降の Collections Framework の改善について淡々と紹介。

簡潔なファクトリメソッド

  • 不変コレクションを生成するための of
    • Collections.unmodifiableList(Arrays.asList(xxx))List.of(xxx) で代替できるようになった
    • Collections.unmodifiableSet(new HashSet<>(xxx))Sets.of(xxx) で代替できるようになった
    • Collections.unmodifiableMap(xxx) にも代替案がある
      • Map.of(k1,v1,k2,v2) みたいな感じ
      • Map.ofEntries(Map.entry(k1, v1), Map.entry(k2, v2)) というのもある
  • 冪等な複製を得るための copyOf
    • 不変コレクションは copyOf しても新しいオブジェクトを生成しない (知らなかった!)
  • Stream から不変コレクションを生成するための Collectors.toUnmodifiableXXX

イテレーション順序のランダム化

  • Set および Map キーの順序の話
    • HashSet と HashMap (のキー) の順序は未定義
    • 新しく JDK がリリースされても順序の一貫性は保たれていた
    • もし順序が変わると既存のコードが壊れる可能性がある
  • 新しいファクトリメソッドが生成する不変コレクションは 順序をランダムにした
    • 潜在的な順序依存性を解消するため
    • 既存の HashSet や HashMap はそのまま
    • 順序を保ちたければ LinkedHashSet LinkedHashMap を使うようにして欲しい

重複する要素の扱い

  • Set.of(a,a) Map.of(k1,v1,k1,v2) Map.ofEntries(...)IllegalArgumentException をスローする
  • リテラルで記述するためのメソッドなのでコーディング誤りの可能性が高いから

空間効率について

ファクトリメソッドはサイズに応じた実装のコレクションオブジェクトを生成する(複製する?)。

素数 実装
0 シングルトン
少数(10以下?) フィールドベースの実装
多数 配列ベースの実装