原文はこちら。
The original article was written by by Ana-Maria Mihalceanu (Developer Advocate, Oracle) and Per-Ake Minborg (Consulting Member of Technical Staff, Oracle).
https://inside.java/2026/06/09/jdk-26-performance-improvements
JDK 26は、Javaプラットフォームの最新の機能リリースであり、2,500件以上の不具合が修正されており、そのうち1,000件以上が機能強化です。Javaプラットフォーム全体で行われているパフォーマンス関連の取り組みをより明確に理解していただくため、本記事では、JDK 26における注目すべきパフォーマンス関連の改善点を、JDKライブラリ、ガベージコレクタ、コンパイラ、ランタイムの4つの主要分野に分類して紹介します。
JDK ライブラリの機能強化
JEP 526: Lazy Constants (Second Preview)
Lazy Constants API のプレビューで、java.lang.LazyConstantが導入されました。これは、必要に応じて初期化される、単一の変更不可能な値を保持するオブジェクトです。オブジェクトの初期化後、JVM はその値を定数として扱うことができるため、コンストラクタやクラス初期化子でeager initializationを行う必要なく、final フィールドで利用可能なものと同様の最適化(定数折り畳み)が可能になります。最大 1 回限りの初期化とスレッドセーフ性は維持しつつ、値が実際に必要になった場合にのみオブジェクトを後から初期化できるため、起動時間が短縮され、不要な処理が削減されます。
このAPIは、JDK 25でStable Values(JEP 502)として初めて登場し、JDK 26で改訂されたバージョンには、早期採用者から寄せられたコミュニティのフィードバックが大幅に反映されています。
JEP 502: Stable Values (Preview)
再設計に伴い、名前が StableValue から LazyConstant に変更され、メソッド(orElseSet、setOrThrow、trySet)は、値計算関数を受け取るファクトリに置き換えられました。また、実行時モデルを簡素化および高速化するため(変更不可能なコレクションや ScopedValue スタイルのセマンティクスと整合させるため)、計算結果として null の使用は許可されなくなりました。また、遅延コレクションのファクトリをListおよびMap(List.ofLazy、Map.ofLazy)に移動することで、発見性も向上しました。
以下のスニペットに示すように、「可変 + null チェック + 同期」というパターンをLazyConstant.of(() -> compute())に置き換え、値が必要な場所でget()を呼び出すことができます。
import java.lang.LazyConstant;
final class Application {
private static final LazyConstant<Service> SERVICE = LazyConstant.of(Service::new);
static Service service() {
return SERVICE.get();
}
}
並行アクセス下であっても、初期化は最大1回のみ行われることが保証されています。複数のスレッドが競合した場合、いずれか1つのスレッドが優先され、安全に値が公開されます。内部的には、このメカニズムは依然として「安定した」フィールドに対する JVM のサポートに依存しているため、遅延値が一度設定されれば、LazyConstant 自体が final フィールドに格納されていることを条件として、繰り返されるアクセスは積極的に最適化されます。この機能は、イージー初期化と最高のパフォーマンスの中間的な役割を果たします。起動時の処理を先送りできますが、値が存在するようになれば、JVM は final 定数と同様にアクセスを最適化する場合があります。
[JDK-8362893] Improve performance for MemorySegment::getString
パフォーマンスに関する議論において、割り当ての省略(allocation elision)とは通常、JITコンパイラが一時的なオブジェクトや配列が実際のヒープ割り当てとして存在する必要がないことを証明し、その割り当てを削除するか、より低コストな操作に置き換えることを指します。いくつかのエッジケースのテストにより、MemorySegment::getStringが内部で一時的な配列を作成しており、その割り当てが最適化によって削除されていないことが判明しました。
JDK 26以降、MemorySegmentからの文字列抽出は、メモリセグメントからJava文字列を作成する際の中間的な割り当てやコピーを削減する実装の恩恵を受けるようになりました。初期のベンチマーク結果では、テスト対象の文字列サイズ全体でレイテンシが低下し、特に短い文字列において大幅な改善が見られました。
パフォーマンスの観点から、この機能強化により、MemorySegment::getStringは、ネイティブデータやヒープ外のデータが頻繁にJava文字列に変換されるホットパス上で利用可能になります。一時的な割り当てやコピーによるオーバーヘッドを削減することで、呼び出しごとのレイテンシを低減し、割り当ての負荷を軽減できるほか、このような変換を頻繁に行うワークロードにおいて、間接的にガベージコレクションの頻度を低減することができます。
[JDK-8366424] Missing type profiling in generated Record Object methods
JDK 26では、レコードクラス用に自動生成されるhashCode()メソッドのパフォーマンスも改善されています。レコードは Map のキーや Set の要素として一般的に使用されるため、hashCode()のパフォーマンスはアプリケーションのスループットに直接影響を与える可能性があります。
この変更により、レコードのハッシュ処理は、手動で記述された実装と同様に効率的に動作するよう最適化されました。その結果、頻繁な検索、グループ化、インデックス作成、または重複排除のためにレコードに大きく依存しているコードでは、スループットの向上が期待できます。
Cryptography Performance Improvements
JDK 26 では、AES、ML-DSA、楕円曲線 P-256 などの暗号アルゴリズムに対して、いくつかの的を絞ったパフォーマンス改善が盛り込まれています。これらの変更により、鍵設定における不要な処理が削減され、低レベルの演算が改善され、CPU 固有の組み込み関数が追加または強化されることで、サポート対象のプラットフォームで一般的な暗号処理をより効率的に実行できるようになります。詳細については、Issue JDK-8371820、JDK-8371450、JDK-8371259、および JDK-8365581 を参照してください。
[JDK-8371820] Further AES performance improvements for key schedule generation – Java Bug System
[JDK-8371450] AES performance improvements for key schedule generation – Java Bug System
[JDK-8371259] ML-DSA AVX2 and AVX512 intrinsics and improvements – Java Bug System
[JDK-8365581] Optimize Java implementation of P256 arithmetic – Java Bug System
Other JDK Library Performance Enhancements and Bug Fixes
JDK-8374644 では、バイト配列やソケットから受信したデータなど、単一の圧縮ストリームを読み込む際の GZIPInputStream のパフォーマンスが向上しています。
[JDK-8374644] Regression in GZIPInputStream performance after JDK-7036144 – Java Bug System
JDK-8359119 では、Charset を更新し、遅延定数アプローチを採用しています。これにより、従来の初期化パターンが新しい API に置き換えられ、この課題には明示的なパフォーマンス評価が記載されています。
[JDK-8359119] Change Charset to use StableValue – Java Bug System
JDK-8371319 では、java.lang.reflect.Method::equals が、同じインスタンスが渡された場合に即座に true を返すよう最適化されています。この実装の利点は、メソッドのディスパッチ中に等価性チェックが頻繁に行われる動的プロキシの実装において顕著です。
Garbage Collection Improvements
JEP 522: G1 GC: Improve Throughput by Reducing Synchronization
G1 は、アプリケーションコードに挿入された書き込みバリアによって維持されるカードテーブルで、リージョン間のポインタ更新を追跡します。一部のワークロードでは参照の更新が頻繁に行われるため、一時停止中にカードテーブルをスキャンするコストが高くなります。そのため、G1 はバックグラウンドでカードテーブルの最適化を行います。この最適化にはアプリケーションスレッドとの同期が必要であり、書き込みバリアや最適化ロジックがより複雑かつ遅くなり、スループットを低下させ、レイテンシにも影響を与える可能性があります。
JEP 522 による変更では、2つ目のカードテーブルが導入されます。これにより、G1の全体的な設計やユーザー向けの動作を変更することなく、アプリケーションスレッドとG1のバックグラウンドカードテーブル最適化スレッド間の同期を削減し、スループット(および若干のレイテンシ)を改善します。アプリケーションスレッドは常に同期なしで「アクティブ」なテーブルを更新するため、ライトバリアが簡素化され、高速化されます。最適化スレッドは、もう一方のテーブル(初期状態では空)に対して独立して処理を行います。G1がアクティブなテーブルのスキャンがポーズ時間の目標値を超えると予測した場合、テーブルをアトミックに切り替えます。
JEP 522で言及されているように、参照の多いワークロードでは、スループットが5~15%向上し、参照の更新が少ない場合でも最大で約5%の向上が確認されました(例:x64の書き込みバリアが約50命令から約12命令に縮小)。ポーズ時間はわずかに短縮され、メモリコストの観点からは、追加のカードテーブルがヒープの約0.2%(ヒープ1GBあたりネイティブメモリ約2MB)を占めます。
JEP 516: Ahead-of-Time Object Caching with Any GC
JDK 26以降、HotSpotのAhead-of-Time(AOT)キャッシュは、ZGCを含むあらゆるガベージコレクタと連携し、低レイテンシのGCとのトレードオフを強いることなく、起動およびウォームアップを改善します。AOTキャッシュは、Javaヒープオブジェクト(例:Classオブジェクトおよびそれらが参照するStringやバイト配列)を、GC固有のインメモリ形式で格納します。これにより、JVMはキャッシュされたオブジェクトをヒープに直接メモリマップし、高速な起動を実現できます。
しかし、ガベージコレクタによってオブジェクト参照の表現方法は異なります。ヒープサイズや領域/大容量オブジェクトの配置ルール(例:G1 対 ZGC)に応じて、圧縮されたポインタと非圧縮のポインタが使用されます。こうした互換性のない参照形式の問題を解決するため、JEP 516では、オプションのGC非依存のオブジェクト形式が追加されました。2つの形式の間にはパフォーマンス上のトレードオフがあります。
JEP 516: Ahead-of-Time Object Caching with Any GC
- GC固有(マッピング可能)のオブジェクトは、キャッシュがすでにファイルシステムキャッシュ内にある可能性が高いため、ウォームスタート時にはほぼ瞬時に読み込める。
- GC非依存(ストリーミング可能)のオブジェクトは、コールドスタート時のディスクレイテンシをより効果的に隠蔽できるが、通常、ストリーミングやマテリアライゼーション処理のために追加のCPUコアを必要とする。
JDKには2つのベースラインAOTキャッシュ(各タイプ1つずつ)が同梱されているため、アプリケーションが独自のキャッシュを提供していない場合でも、JVMはマッピングとストリーミングのどちらかを選択できます。JVMは、トレーニング後にどのフォーマットを生成するかを決定するためにヒューリスティックを適用します:
- トレーニングでZGC、
-XX:+UseCompressedOopsオプション、または32GBを超えるヒープ (これは制約の少ないシステムであることを示唆する)場合は、ストリーミング可能かつGC非依存の形式を選択する。 - トレーニングで圧縮Oopsが使用された場合(これは制約の多いシステムであることを示唆する)は、マッピング可能かつGC固有の形式を優先する。GC非依存のストリーミングを強制するには、
-XX:+UseCompressedOopsオプションを指定している場合でも、-XX:+AOTStreamableObjectsオプションを追加する。
この変更により、アプリケーションがガベージコレクション戦略を変更することなく、より広範なアクセスが可能になり、起動およびウォームアップが高速化されるパフォーマンス上のメリットを享受できます。
[JDK-8371986] Remove the default value of InitialRAMPercentage
JDK 26 では、明示的なヒープサイズを設定していない場合、デフォルトの初期 Java ヒープサイズを小さくすることで、JVM の起動パフォーマンスが向上しています。
以前は、ユーザーが -Xms または -XX:InitialHeapSize を使用して初期ヒープサイズを設定しなかった場合、JVM は InitialRAMPercentage からそれを算出していました。そのデフォルト値は、物理メモリの 1.5625%、つまりシステム RAM のおよそ 1/64 でした。メモリ容量の大きなマシンでは、これにより起動時に比較的大きなヒープが準備されてしまう可能性がありました。
この変更により、JVMはデフォルトのInitialRAMPercentageを適用しなくなりました。代わりに、初期ヒープサイズを指定しない場合、JVMは可能な限り最小のヒープサイズであるMinHeapSizeで起動します。この変更によるパフォーマンスへの影響は、デフォルトのJVM設定を使用するアプリケーションで最も顕著です。初期段階で初期化するヒープメタデータを減らすことで、JVMはより早く実行を開始できると同時に、アプリケーションがより多くのメモリを必要とするようになった際に、後でヒープを拡張することも可能になります。
Compiler Improvements
[JDK-8325467] Support methods with many arguments in C2
HotSpot の階層型コンパイルモデルでは、コードは通常、インタプリタで実行が開始され、ウォームアップを早めるために C1 によって迅速にコンパイルされる場合があり、その後、コードがhotになると C2 によってより積極的に最適化されます。JDK 26 以降、C2 JIT コンパイラは、非常に長いパラメータリストを持つメソッドを処理できるようになりました。これにより、頻繁に実行されるコードは、C1やインタプリタといった最適化レベルの低い実行パスにとどまることなく、C2でコンパイルされるようになります。
C2の最適化の対象となるメソッドが増えることで、アプリケーションの変更を必要とせずに、スループットの向上とCPUオーバーヘッドの低減が図れます。C2 JITコンパイラの仕組みについて詳しく知りたい場合は、Emanuel Peter氏のブログを読むことをお勧めします。
Introduction to HotSpot JVM C2 JIT Compiler, Part 0 | Emanuel’s Blog
[JDK-8340093] C2 SuperWord: implement cost model
JDK 26 では、ループのベクトル化に関する JIT コンパイラのコストモデリングが引き続き改善されており、SIMD 形式の実行が実際に有益であるタイミングについて、より適切な判断を下せるようになっています。ベクトル化は複数の値を一度に処理することでループを高速化できますが、データのシャッフルやパッキングなど、余分な作業を伴う場合もあります。
JDK 26で実装された機能は、スカラーループ演算をベクトル演算に変換する価値があるかどうかを判断するために使用されるコストモデルを強化するものです。ベクトル化は、複数の値を一度に処理することでスループットを大幅に向上させることができますが、ベクトルデータの準備や結合に必要な余分な作業が、得られる利益を上回らない場合にのみ有益です。
これらの C2 の改善についてさらに詳しく探求するために、Emanuel Peter 氏はC2 AutoVectorizer Improvement Ideasを執筆しています。同氏は C2 開発のより広範な方向性を概説しており、またVectorizing Reductions: from JDK 9 to JDK 26 and beyondでは、リダクションのベクトル化が時間とともにどのように進化してきたかについて、より詳細な背景情報を提供しています。
C2 AutoVectorizer Improvement Ideas | Emanuel’s Blog
Vectorizing Reductions: from JDK9 to JDK26 and beyond | Emanuel’s Blog
Runtime Improvements
[JDK-8369238] Allow virtual thread preemption on some common class initialization paths
複数の仮想スレッドが、まだ初期化中のクラスに遭遇した場合、その初期化が完了するまで待機する必要がある場合があります。このような待機により、仮想スレッドがキャリアスレッドに結合されたままになり、他の仮想スレッドを実行するために利用可能なキャリアの数が減少する可能性があります。
JDK 26 では、一般的なクラス初期化パスにおいて待機中の仮想スレッドをプリエンプトできるようにすることで、不要なキャリアのブロッキングを軽減し、多数の仮想スレッドを持つアプリケーションのスケーラビリティを向上させるとともに、クラス読み込みや初期化が集中する際に発生するスループットの低下やキャリア不足のリスクを低減します。
Closing Thoughts
JDK 26は2026年3月から一般提供されているため、今こそご自身のアプリケーションやワークロードで試してみる絶好の機会です。
テストや移行計画を行う際は、現在使用しているJDKバージョンと比較して、JDK 26上でのアプリケーションのパフォーマンスを必ず測定してください。リグレッションと思われる挙動に気付いた場合は、ぜひ参加していただくか、関連するメーリングリストで問題を報告してください。本番環境に近いワークロードからのフィードバックは、Javaプラットフォームの継続的な改善に役立ちます。
JDK 27に向けて数多くの改善が具体化しつつある中、JDKのパフォーマンス向上に向けた取り組みは続いており、今後のアップデートでそれらについてさらに詳しくご紹介できることを楽しみにしています。
それまでの間も…Fast Pathを走り続けてください!