- 前川喜洋
- 約 5,600 文字
はじめに
Java連載2026 の3本目です。2026年3月にリリースされたJava 26と、9月のJava 27について、主なJEPとJEP以外の運用に関わる変更、アップグレード時の注意点をまとめます。
Java 26(2026年3月)の主なJEP
| JEP | 機能 | ステータス |
|---|---|---|
| 517 | HTTP/3 for the HTTP Client API | 正式 |
| 516 | Ahead-of-Time Object Caching with Any GC | 正式 |
| 522 | G1 GC: Improve Throughput by Reducing Synchronization | 正式 |
| 500 | Prepare to Make Final Mean Final | 正式 |
| 504 | Remove the Applet API | 削除 |
| 526 | Lazy Constants | 第2プレビュー |
| 524 | PEM Encodings of Cryptographic Objects | 第2プレビュー |
| 530 | Primitive Types in Patterns, instanceof, and switch | 第4プレビュー |
| 525 | Structured Concurrency | 第6プレビュー |
| 529 | Vector API | 第11インキュベータ |
HTTP/3対応(JEP 517)
Java 11で導入されたHttpClientがHTTP/3(QUIC)に対応しました。既定は引き続きHTTP/2で、HTTP/3はビルダーで.version(HttpClient.Version.HTTP_3)を指定するオプトインです。外部ライブラリなしで多重化と低レイテンシの恩恵を受けられます。
AOTオブジェクトキャッシュの全GC対応(JEP 516)
Project Leyden由来のAOTキャッシュ(訓練実行でロード・リンクしたクラスや初期化済みオブジェクトを保存し、次回以降の起動で再利用する仕組み)が、GCに依存しない形式で初期化済みオブジェクトを逐次ロードできるようになりました。これによりZGCでも起動・ウォームアップ高速化を使えます。
手元の小さなアプリで試したところ、Java 25のZGCではオブジェクトを保存できず(ZGC is not supported)、キャッシュを使っても起動は速くなりませんでした。Java 27ではZGCでもG1と同じように、起動時間が大きく短縮されました。
なお、既定の-XX:AOTMode=autoではキャッシュを使えなくてもそのまま起動を続けます。キャッシュが効いているかを確かめるときは-XX:AOTMode=onを付けると、使えない場合に起動が失敗するので気づけます。
finalを本当にfinalにする準備(JEP 500)
武田さんの こちらの記事で詳しく解説しています。
Lazy Constants(JEP 526、第2プレビュー)
旧StableValueから改名・整理されたLazyConstantです。初回アクセス時に一度だけ計算され、以降はJITが定数として最適化できます。ダブルチェックロッキングやHolderクラスイディオムの置き換えになります。
// --enable-preview が必要 |
ただし、初期化に失敗したときの挙動は一般的なダブルチェックロッキングと違います(JDK 27で確認)。
- 初期化中に例外が出ると、その失敗が記録されて再計算されません。以降の
get()は常にNoSuchElementExceptionになります現行の実装が「一時的な失敗のあと再試行によりいずれ成功する」ことを期待する仕組みになっている場合、LazyConstantに置き換えると回復できなくなります。 - 初期化の処理が
nullを返した場合も失敗として記録されます(原因はNullPointerException)。値がないことを表したいときはOptionalなどで包みます
JEP以外の重要な変更
ここでは運用への影響が大きい初期ヒープサイズの既定値変更だけを取り上げます。そのほかの変更はJDK 26 Release Notesを参照してください。
InitialRAMPercentageの既定値廃止(JDK-8371986、CSR JDK-8371987)
-Xmsを指定しない場合の初期ヒープは、これまで物理メモリの1/64でした。Java 26ではInitialRAMPercentageの既定値が0になり、初期ヒープはMinHeapSizeと同じ値になります。起動は速くなりますが、起動直後にヒープ拡張のGCが増えることがあります。スループットや起動直後のレイテンシが悪化した場合は、-Xms512mのように初期ヒープを明示しましょう。
Java 27(2026年9月)の主なJEP
テーマは 「良い既定値をオプトインなしで有効にする」 です。G1とコンパクトオブジェクトヘッダのデフォルト化は、コードを変えなくても挙動が変わります(InfoQ)。
| JEP | 機能 | ステータス |
|---|---|---|
| 523 | Make G1 the Default Garbage Collector in All Environments | 正式 |
| 534 | Compact Object Headers by Default | 正式 |
| 527 | Post-Quantum Hybrid Key Exchange for TLS 1.3 | 正式 |
| 536 | JFR In-Process Data Redaction | 正式 |
| 531 | Lazy Constants | 第3プレビュー |
| 538 | PEM Encodings of Cryptographic Objects | 第3プレビュー |
| 532 | Primitive Types in Patterns, instanceof, and switch | 第5プレビュー |
| 533 | Structured Concurrency | 第7プレビュー |
| 537 | Vector API | 第12インキュベータ |
G1が全環境でデフォルトに(JEP 523)
これまでJVMは「サーバークラスマシン」と判定しない小さな環境(例: 1 CPU・低メモリのコンテナ)でSerial GCを選んでいました。Java 27からは環境に関わらずG1が既定です。意図せずSerial GCで動いていた小さなPodは、フラグなしでG1になります(The Register)。
メモリが極端に小さい環境で従来どおりにしたい場合は-XX:+UseSerialGCを明示します。どのGCが選ばれているかは次のコマンドで確認できます。
java -Xlog:gc -version |
コンパクトオブジェクトヘッダがデフォルトに(JEP 534)
武田さんの こちらの記事で詳しく解説しています。
TLS 1.3の耐量子ハイブリッド鍵交換(JEP 527)
従来のECDHEとNIST標準のML-KEMを組み合わせたハイブリッド鍵交換(X25519MLKEM768など)がJSSEに入りました。「今盗聴して将来復号する(Harvest Now, Decrypt Later)」攻撃への対策で、標準のSSLSocketやHttpClientを使っていればアプリコードの変更なしで恩恵を受けられます。
注意したいのはClientHelloの大きさです。ML-KEM-768の公開鍵(1184バイト)が乗るため、手元の計測ではJava 25の422バイトからJava 27では1578バイトに増えました。一般的なMTU(1500バイト)を超えて複数のTCPセグメントに分かれるので、ClientHelloが1パケットに収まる前提で作られた古いミドルボックスやロードバランサで接続できなくなることがあります。問題が出た場合は、鍵交換グループを従来のものに絞ると元の大きさ(約410バイト)に戻せます。
java -Djdk.tls.namedGroups=x25519,secp256r1 -jar app.jar |
JFRのプロセス内データ秘匿化(JEP 536)
Java Flight Recorderが、記録を確定させる前に機密情報を取り除けるようになりました。本番環境のJFRファイルをベンダーや別チームに渡す運用がしやすくなります。
秘匿化は既定で有効です。キー名が*password*、*secret*、*token*などに一致する環境変数・システムプロパティの値(redact-key)と、一致するコマンドライン引数(redact-argument)が[REDACTED]に置き換わります。パターンは追加もできます。
# 既定パターンに *cred* を追加(先頭の + で追記、付けないと置き換え) |
秘匿化はベストエフォートです。jdk.ProcessStart(子プロセスの引数)やjdk.InitialSecurityPropertyは対象外なので、JFRファイルを外部に渡す前の確認は引き続き必要です。対象になるイベントはjava -XX:FlightRecorderOptions:helpで確認できます。
プレビューの継続
プリミティブ型パターンを使うと、次のように値の範囲で安全に分岐できます。
// --enable-preview が必要 |
「情報を失わずに変換できるか」は、キャストで往復して元に戻るかではなく、値そのものが変わらないかで判定されます。境界値では次のようになります(JDK 27で確認)。
| 式 | 結果 | 理由 |
|---|---|---|
-0.0に対するcase int i |
マッチしない | 0に変換すると符号が失われる |
Long.MAX_VALUE instanceof double d |
false |
doubleでは正確に表せない。一方(long)(double)Long.MAX_VALUEは飽和によって元の値に戻るため、キャストの往復で調べる手書きのチェックは誤って通ってしまう |
Object o = 42;のあとのo instanceof long l |
false |
IntegerはLongではないのでマッチしない。数値の拡大変換はプリミティブ型同士でだけ行われる |
Java 26からStructured Concurrencyを使っている場合は、Joiner.onTimeout()がR timeout()に変わった点に注意が必要です。timeout()は抽象メソッドなので、Java 26に合わせて自作したJoinerはJava 27ではコンパイルできません。また、検査例外が型パラメータR_Xで表されるようになり、allSuccessfulOrThrow(Function<Throwable, R_X>)のように投げる例外の型を呼び出し側で選べます。
JEP以外の重要な変更
ここでは、指定したままだと起動できなくなるものと、気づかないうちに無視されるようになるものを取り上げます。そのほかの変更はJDK 27 Runtime Updatesを参照してください。
| 変更 | チケット | 影響 |
|---|---|---|
-noverify、-Xverify:noneを削除 |
JDK-8373481 | 指定したままだと起動できない。代替なし |
MaxRAM、AggressiveHeap、AlwaysActAsServerClassMachine、NeverActAsServerClassMachineが廃止(obsolete) |
— | Java 26で非推奨化。指定すると警告が出て無視され、Java 28で起動エラーに |
Java 25(LTS)ではこれらのフラグを指定しても警告が出ないため、LTSから直接上げると、非推奨の段階を一度も目にしないまま無視されるようになります。MaxRAMでコンテナのメモリ認識を調整していた場合は-Xmxや-XX:MaxRAMPercentageに移しましょう。廃止時期はHotSpotのソースにあるフラグの廃止予定表(arguments.cpp @ jdk-27-gaのspecial_jvm_flags)で確認しました。
アップグレード時の注意点
本番はLTS(Java 21/25)のまま、CIでJava 27のビルドとテストを回して次期LTSのJava 29(2027年9月予定)に備えるのが現実的な進め方です。Java 26はJava 27のリリースで更新が止まっているので、非LTSを使うならJava 27へ上げましょう。
- 初期ヒープサイズの既定変更(Java 26): 起動直後のGC回数とレイテンシを比べ、必要なら
-Xmsを明示します - GCの既定変更(Java 27): 小さなコンテナでSerial GCからG1に切り替わります。メモリ使用量と停止時間を計測し直します
- コンパクトオブジェクトヘッダ(Java 27): ヘッダレイアウトに依存するネイティブコードや
Unsafe利用ライブラリがないか確認します - finalフィールド書き換えの警告(Java 26〜): 起動ログの警告を洗い出し、ライブラリを更新します。原因の特定には
--illegal-final-field-mutation=debug、将来の禁止の事前確認には=denyが使えます。将来の禁止に備えて、恐らく最も影響が広い項目です - Applet APIの削除(Java 26):
java.appletへの参照が残っているとコンパイルできません - 削除・廃止された起動オプション(Java 27): 起動スクリプトやDockerfile、APMエージェントの導入手順に
-noverifyやMaxRAMなどが残っていないか検索します - TLSの鍵交換(Java 27): 古いミドルボックスやロードバランサを経由する接続をテストします
プレビュー機能(--enable-preview)はリリースごとにAPIが変わります。実際Java 26→27の間でもStructured ConcurrencyやPEMのAPIが見直されているため、本番コードでの利用は避けましょう。
まとめ
Java 26はHTTP/3やAOTキャッシュの拡張といった「使える道具」を増やし、Java 27はG1とコンパクトオブジェクトヘッダを既定化して「何もしなくても速く軽く」しました。耐量子TLSの正式化は、セキュリティ要件の厳しいシステムにとって大きな前進です。
次のLTSはJava 29(2027年9月予定)です。今のうちにJava 27でCIを回し、finalフィールドの警告やGC変更の影響を潰しておくと、次のLTS移行が楽になります。