Contents
Java 21 概要と LTS の意義
Java 21 は長期サポート(LTS)リリースで、エンタープライズでの採用判断に直結します。Java 21 には仮想スレッド(Virtual Threads)や構造化並行処理(Structured Concurrency)、パターンマッチング等の改良が含まれ、既存の同期コードの近代化が進みます。導入前は互換性検証とベンダーごとのサポート条件確認を必ず行ってください。
主要新機能(JEP/公式リンク・ステータス・コード例・移行観点)
以下では Java 21 の主要機能について、該当JEPと公式リリースノートへの参照、簡潔なコード例、移行でのチェックポイントを示します。各機能の正式ステータス(標準/プレビュー/インキュベータ)を明記します。
仮想スレッド(Virtual Threads)
仮想スレッドは軽量スレッドで、ブロッキングI/O中心の同期コードをほとんど書き換えずに高並行化できます。
- 該当JEP・公式情報
- JEP 425 — Virtual Threads(初出: Preview): https://openjdk.org/jeps/425
- JDK 21 リリースノート(公式まとめ): https://openjdk.org/projects/jdk/21/
- ステータス(JDK 21 における状態): 標準(standard)として利用可能です。詳細は上記リリースノートを参照してください。
仮想スレッドの短い説明と利点は次の通りです。生成コストが小さく、I/O待ちにより OS スレッドが枯渇しにくくなります。一方で OS リソース(ファイルディスクリプタ等)は依然制限対象です。
実行可能なサンプル(同期 HTTP を仮想スレッドで並列取得、例外・タイムアウトを明示):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
// VirtualThreadsHttpFetch.java import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; public final class VirtualThreadsHttpFetch { public static void main(String[] args) { HttpClient client = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(5)) .build(); List<URI> uris = List.of( URI.create("https://www.example.com"), URI.create("https://www.example.org") ); try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) { List<Future<String>> futures = new ArrayList<>(); for (URI uri : uris) { futures.add(executor.submit(() -> { HttpRequest req = HttpRequest.newBuilder(uri) .timeout(Duration.ofSeconds(10)) .GET() .build(); HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString()); return res.body(); })); } for (Future<String> f : futures) { try { String body = f.get(30, TimeUnit.SECONDS); System.out.println("body.length=" + (body != null ? body.length() : 0)); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); System.err.println("Interrupted: " + ie); } catch (ExecutionException ee) { System.err.println("Task failed: " + ee.getCause()); } catch (TimeoutException te) { System.err.println("Task timeout: " + te); } } } } } |
コンパイル/実行(一般的な JDK 21 配布物では追加フラグ不要):
|
1 2 3 |
javac --release 21 VirtualThreadsHttpFetch.java java VirtualThreadsHttpFetch |
移行チェック(仮想スレッド):
- JNI やネイティブライブラリでブロッキングする箇所がないか確認する。ネイティブ長時間ブロックは OS スレッドを占有します。
- ThreadLocal の利用を洗い出し、仮想スレッドでメモリ増加を招く箇所を特定する。
- OS のファイルディスクリプタ上限(ulimit -n)や外部サービス接続数の上限を確認する。
構造化並行処理(Structured Concurrency)
複数の並列作業を「スコープ」でまとめて扱い、例外伝播・キャンセルを一括管理できます。失敗時に他の作業を即座に打ち切るなどの設計が容易になります。
- 該当JEP・公式情報
- JEP 428 — Structured Concurrency (Incubator): https://openjdk.org/jeps/428
- JDK 21 リリースノート: https://openjdk.org/projects/jdk/21/
- ステータス: インキュベータ(incubator) — API が安定化するまで変更の可能性があります。
簡単な使い方と注意点を示します。Structured Concurrency は incubator モジュールに含まれているため、コンパイルと実行時にモジュールの明示が必要になる場合があります。
実行可能なサンプル(StructuredTaskScope を使った並列フェッチ):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// StructuredConcurrencyExample.java import jdk.incubator.concurrent.StructuredTaskScope; import java.util.concurrent.Future; public final class StructuredConcurrencyExample { public static void main(String[] args) throws Exception { try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { Future<String> a = scope.fork(() -> fetchA()); Future<String> b = scope.fork(() -> fetchB()); scope.join(); // 子タスクを待つ scope.throwIfFailed(); // 失敗があれば例外を投げる System.out.println(a.resultNow() + b.resultNow()); } } static String fetchA() throws Exception { Thread.sleep(100); return "A"; } static String fetchB() throws Exception { Thread.sleep(200); return "B"; } } |
コンパイル/実行(incubator モジュールを指定):
|
1 2 3 |
javac --release 21 --add-modules jdk.incubator.concurrent StructuredConcurrencyExample.java java --add-modules jdk.incubator.concurrent StructuredConcurrencyExample |
移行チェック(Structured Concurrency):
- 既存の例外処理/キャンセル設計がスコープ方式に適合するか評価する。
- Incubator API は将来変更されるため、ライブラリ化して差し替えやすくする。
パターンマッチング(Pattern Matching)
型判定と分岐が簡潔になり、冗長なキャストが減ります。switch に対するパターンマッチングは言語のプレビュー機能として段階的に追加されています。
- 該当JEP・公式情報
- JEP 441 — Pattern Matching for switch(プレビュー段階のバージョンがある場合あり): https://openjdk.org/jeps/441
- JDK 21 リリースノート: https://openjdk.org/projects/jdk/21/
- ステータス: プレビュー(preview) — コンパイル/実行に --enable-preview が必要な場合があります。
簡単な switch のパターン例(プレビュー機能):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// PatternSwitchExample.java public class PatternSwitchExample { public static String describe(Object o) { return switch (o) { case String s -> "string:" + s; case Integer i -> "int:" + i; case null -> "null"; default -> "other"; }; } public static void main(String[] args) { System.out.println(describe("abc")); System.out.println(describe(123)); } } |
コンパイル/実行(プレビュー機能を利用する場合):
|
1 2 3 |
javac --release 21 --enable-preview PatternSwitchExample.java java --enable-preview PatternSwitchExample |
移行チェック(パターンマッチング):
- リフレクションやシリアライズで型情報に依存する箇所を確認する。
- プレビュー言語機能は将来変わる可能性があるため、フラグ付きで段階的に採用する。
その他の改良(診断・API改善)
JDK 21 は小規模な API 改善や診断機能の強化を含みます。詳細は JDK 21 の公式リリースノートと個別 JEP を参照してください(上記リンク参照)。
実務ハンズオン:仮想スレッドと構造化並行処理の検証手順
この章ではローカルやステージング環境での検証手順と、実運用に向けたポイントを整理します。まずは影響範囲が限定できる I/O バウンドモジュールでパイロットを行ってください。
準備と前提
検証前に行うべき基本作業を示します。検証は再現性を高めるために環境を固定して実施してください。
- JDK 21 の入手とベンダー選定(Oracle, Eclipse Temurin, Amazon Corretto, Azul など)
- CI の JDK 21 イメージを用意し、ローカルと CI の実行環境を揃える
- 外部依存(DB、外部API)はスタブ化または固定応答にする
Virtual Threads のハンズオン(要点)
実稼働に近い条件で検証します。以下は重要な観測点です。
- 生成レートと総数(単位時間あたりの仮想スレッド生成数)をログに出す。
- OS スレッド数(jstack / ps)と仮想スレッド数を別に計測する。
- オープンファイル数(lsof -p
| wc -l)を定期計測する。 - ThreadLocal 使用状況を確認し、スコープ外リークがないか点検する。
Structured Concurrency のハンズオン(要点)
StructuredTaskScope を用いたコードは、失敗時の早期キャンセルやタイムアウト伝搬を確認します。
- 部分失敗時に他タスクが確実にキャンセルされるか確認する。
- タイムアウト戦略(スコープ単位 vs Future.get(timeout))を検討し、負荷条件での動作を比較する。
- Incubator API のバージョン差分に備え、抽象化レイヤーで囲むことを検討する。
性能評価とベンチマークの実施方法
ベンチマークは再現性が最重要です。ここでは具体的な測定プロトコルと統計的配慮を示します。
測定プロトコル(再現性担保)
まずは環境と手順を固定します。短くても以下を守ってください。
- ベースライン(例: Java 17)で測定し、比較対象を確定する。
- ハードウェア/VM のリソース割当を固定する(CPU、メモリ、cgroup 設定)。
- CPU の電源管理を固定(例: performance ガバナ)し、バックグラウンド負荷を排除する。
- ウォームアップ期間を必ず入れる(JMH の場合 -wi を利用)。
- 複数回(fork を変えた全体試行を 3〜5 回以上)実行してばらつきを評価する。
- 外部依存はスタブ化し、ネットワーク変動を排除する。
- GCログ、JFR、OSメトリクスを同時に収集する。
JMH の設定例と統計的配慮
JMH を用いたマイクロベンチでは以下の設定が目安です。
- 推奨設定例: -wi 10 -i 20 -f 3 (ウォームアップ10回、計測20回、フォーク3)
- さらに再現性を高めるために「全フォーク試行」をスクリプトで 3〜5 回繰り返す。
- 結果は平均だけでなく 95% 信頼区間(CI)や標準偏差を提示する。JMH の出力を CSV にして統計解析ツールで CI を算出すると良いです。
- 異常値は除外せず、何が外れ値を生んだか(GC、OSスケジューラ、クロスノイズ)を調査する。
JMH 実行例:
|
1 2 3 4 |
java -jar target/benchmarks.jar \ -wi 10 -i 20 -f 3 -r 1s \ -prof gc |
(上記を複数回実行して fork ごとのばらつきを確認する)
Virtual Threads 固有の観点
- 仮想スレッドの累積生成数とスレッド寿命分布を測定する。
- OS スレッド数(carrier threads)の推移を追い、想定外の増減がないか確認する。
- FD(ファイルディスクリプタ)使用率を連続監視する(Prometheus exporter 等で可視化)。
移行ガイドと運用上の注意(チェックリストと段階的プラン)
移行は段階的に行い、定量的な受入基準(KPI)で判断します。以下は実務で使えるチェックリストと手順です。
事前評価(コード/依存関係/インフラ)
検査項目と代表的なコマンド例です。一次情報確認を優先してください(ベンダーの互換情報、JEP、OpenJDK リリースノート)。
-
内部 API 依存の検出: jdeps を利用して internal API 呼び出しを抽出する。
例: jdeps --jdk-internals -s path/to/your.jar -
JNI/ネイティブの有無: ランタイムで System.loadLibrary 呼び出しや native メソッドを検索する。
- 反射/シリアライズの影響箇所をコードレビューで特定する。
- 使用ライブラリの公式互換性情報を確認する(Netty、JDBC ドライバ、Hibernate、Spring 等)。
ビルドツールとCIの準備
- Maven/Gradle のラッパーを JDK 21 に切替えて全テストを走らせる。
- CI イメージを JDK 21 ベースに切り替え、静的解析と依存性チェックを追加する。
- コンパイルでプレビュー機能を使う場合は javac と java に --enable-preview を付与するジョブを用意する。
コンテナ/デプロイ設定(実務値の例)
- Docker の ulimit 設定例: --ulimit nofile=65536:65536
- 推奨 ulimit(目安): 最低 16384、並列接続が多ければ 65536 を検討。
- JVM のメモリ設定: まずは堅牢な Xms/Xmx で固定。例: -Xms512m -Xmx2g
- GC ログとメトリクス出力: -Xlog:gc*:file=/var/log/gc.log:time,tags
パイロット実施計画(推奨手順)
- 対象モジュール選定:I/O バウンドで外部呼び出しが多いサービスを選ぶ。
- ベースライン測定:現行(例 Java 17)でスループット/レイテンシ/メモリを取得。
- 仮想スレッド版に差し替え、同条件で再測定。
- 比較評価:p50/p95/p99、スループット、GC時間、FD 使用量、エラー率。
- 受入基準に合致する場合に段階ロールアウトへ移行。
ロールアウト手順(本番導入)と監視指標(閾値例)
段階的ロールアウトと具体的な閾値例を示します。閾値はサービスの性質で調整してください。
- ロールアウト工程: スモークテスト → カナリア(1%)→ 拡大(10% → 50% → 100%)
- 推奨モニタリング閾値例(例示):
- p95 レイテンシがベースライン比 +30% かつ絶対値 +200ms で Warning、+100% かつ +500ms で Critical。
- エラー率(HTTP 5xx 等): >0.1%(Warning)、>0.5%(Critical)。
- CPU 使用率(vCPU 単位): 平均 > 70%(Warning、持続 5 分)、> 90%(Critical)。
- GC pause: 連続で > 200ms(Warning)、> 500ms(Critical)。
- オープンファイル数: > 70% of limit(Warning)、> 90%(Critical)。
受入基準/ロールバック KPI の例:
- 主要 KPI(p95, error_rate, GC pause, FD)いずれかが Critical 条件を満たしたら即時ロールバック。
- カナリア段階で 15 分間安定していなければロールバック。
Prometheus アラート(例・擬似):
|
1 2 3 4 5 6 7 8 |
- alert: JavaServiceHighP95 expr: (histogram_quantile(0.95, sum(rate(http_server_request_duration_seconds_bucket[5m])) by (le))) > (baseline_p95 * 1.3 + 0.2) for: 5m labels: severity: warning annotations: summary: "p95 latency increased for Java service" |
注意点(ThreadLocal / FD / ネイティブまとめ)
仮想スレッド導入で特に注意すべき点を一箇所にまとめます。
- ThreadLocal: 仮想スレッドは大量に生成されるため、スレッドローカルに大きなオブジェクトを置くとメモリ増を招きます。ThreadLocal 使用を最小化し、必要時は弱参照や明示的クリーンアップを行う。
- ファイルディスクリプタ(FD): 仮想スレッドの並列度が上がると同時接続数とFD消費が増える。ulimit の見直しと外部サービス側の同時接続上限を確認する。
- ネイティブライブラリ/JNI: 長時間ブロッキングするネイティブコードは carrier thread を占有する。ネイティブ重処理はプラットフォームスレッドに分離する、または専用プールを用意する。
- ライブラリ互換: Netty(特にネイティブトランスポート)、古い JDBC ドライバ、ネイティブ拡張を持つライブラリは事前検証が必要。ベンダーの互換性ページを確認する。
- ログとメトリクス: 仮想スレッド数・生成率・FD・GC・スレッドダンプの収集を標準化しておく。
参考リンク(一次情報を優先)
以下は公式ドキュメントや一次情報を優先した参照先です。変更履歴や最新のステータスは各ページを直接確認してください。
-
OpenJDK: JDK 21 プロジェクトページ(リリース情報)
https://openjdk.org/projects/jdk/21/ -
OpenJDK: JEP リスト(各 JEP の公式ページへ)
https://openjdk.org/jeps/ -
JEP 425 — Virtual Threads
https://openjdk.org/jeps/425 -
JEP 428 — Structured Concurrency (Incubator)
https://openjdk.org/jeps/428 -
JEP 441 — Pattern Matching for switch
https://openjdk.org/jeps/441 -
Project Loom(OpenJDK): 仮想スレッド関連ドキュメント
https://openjdk.org/projects/loom/ -
Oracle JDK 21 リリースノート(ベンダー側の公式説明)
https://www.oracle.com/java/technologies/javase/21-relnotes.html -
Eclipse Temurin(Adoptium) リリース情報(JDK ビルド配布)
https://adoptium.net/ -
Amazon Corretto(サポート情報)
https://aws.amazon.com/corretto/ -
Azul Zulu(サポート情報)
https://www.azul.com/ -
JMH(マイクロベンチマークツール)公式(OpenJDK のツール)
https://openjdk.org/projects/code-tools/jmh/
各フレームワーク・ライブラリの互換性は、Spring、Netty、Hibernate、各 JDBC ドライバ等の公式互換ページを必ず参照してください。
まとめ
Java 21(LTS)は、仮想スレッドや構造化並行処理、パターンマッチングなどの機能で同期コードのスケーリングと可読性を向上させます。導入は段階的に行い、ベースラインとの比較、互換性チェック、監視と明確なロールバックKPIを定めてください。まずは I/O バウンドの限定モジュールでパイロットを行い、公式 JEP とベンダーのリリースノートを参照しながら本番導入を進めてください。