10年の歩み:Project ValhallaがJDK 28で完成させること
10年の歩み:Project ValhallaがJDK 28で完成させること
Javaはなぜ10年を経てようやく「intのように動くオブジェクト」を実現できるようになったのか——その答えはJVMの根本設計をどう変えるのか。
導入
Javaの強みは長らく逆説を抱えていた。オブジェクト指向の抽象化によって複雑なドメインモデルを明確に表現できる一方、その表現力には常にパフォーマンスコストが伴った。整数一つをintで宣言すれば、JVMは4バイトをスタックに直接ロードする。だがIntegerオブジェクトで包んだ瞬間にヘッダーメタデータが付き、ヒープのどこかに配置され、GCが追跡しなければならない参照がひとつ増える。金融計算、座標処理、データ分析のように数千万個の小型データを扱うドメインでは、この乖離が性能ボトルネックに直結した。
いわゆる「ポインター地獄」である。数億個のオブジェクトがヒープ上に散在して互いを指し合う構造では、CPUキャッシュが機能しない。メモリアクセスパターンが不連続なためハードウェアプリフェッチャーが予測に失敗し、GCは生きているオブジェクトグラフを走査するために止まる。CやRustでは構造体の配列がメモリ上に連続配置されるのが当然の前提であるのに対し、Javaではそれが不可能だった。
日本の大手SIerや金融機関、公共システムの多くはJavaを基幹言語として採用してきた。Java EEベースのシステムが依然として金融決済や公共インフラの中枢を担っている現状では、パフォーマンス改善はハードウェア増強で対処してきた側面が大きい。Project Valhallaが提示するのは、そのアーキテクチャ的前提を言語レベルで覆す可能性である。
Project Valhallaはこの根本的非効率を言語レベルで解消しようとする取り組みだ。2014年にBrian Goetzが公開メーリングリストに草案を投稿して以来、約12年。JDK 28でプレビュー機能として初めて出荷された。Hacker Newsに掲載された「Project Valhalla, Explained」という記事は投稿当日に656ポイントを記録し、Javaコミュニティの熱い反応を集めた。コメント欄には「ついに」という一言が繰り返された。
Value Classesの解剖:プリミティブのように動くオブジェクト
既存Javaオブジェクトモデルの構造的限界
Javaのオブジェクトはすべて参照型である。配列Color[] colorsを宣言すると、ヒープ上に実際に格納されるのはColorインスタンスそのものではなく、それを指すポインターの配列だ。各Colorインスタンスはヒープの任意の場所に分散配置され、その前には12〜16バイトのオブジェクトヘッダーが付く。RGBA色一つを表すために実際に必要なデータは4バイトであるにもかかわらず、オブジェクトで包んだ瞬間に実際のメモリ使用量は4〜5倍に膨らむ。
キャッシュミスはここで発生する。CPUは64バイト単位のキャッシュラインを取得する。配列の走査時にCPUは「次の要素も近くにあるはず」と予測してプリフェッチを行う。しかし配列にポインターしかなければ、ポインターを読んで初めて実際のデータ位置がわかる。数百万回の無作為なメモリアクセスが発生し、CPUはデータを待ちながらサイクルを浪費する。
JVMのエスケープ解析(Escape Analysis)はこの問題を部分的に回避できる。オブジェクトがメソッドの外に「エスケープ」しないとJITが確信できる場合は、ヒープ割り当てをスキップしてスタック上でスカラー分解する。しかしこの最適化は本質的に不安定だ。コード変更一つ、ライブラリ更新一つでJITの判断が変わり得る。開発者はどのコードが最適化されているかを知る術がない。パフォーマンスがJITのブラックボックスに委ねられているという不確実性は、高性能システム開発における根本的な障壁だった。
Value Classes:言語レベルの解法
JDK 28のValue Classesは、value修飾子をクラス宣言に追加することから始まる。
value class Color {
private final int red;
private final int green;
private final int blue;
private final int alpha;
}
この宣言一つで複数のことが変わる。
同一性(identity)がない。 通常のオブジェクトでは==演算子が参照の同一性を検査する。同じ内容を持つ二つのオブジェクトでも、ヒープ上の異なるアドレスにあれば==はfalseを返す。Value Classでは==が置換可能性(substitutability)を検査する。すべてのフィールドが同じであれば==がtrueを返す。これはintやdoubleのようなプリミティブの動作と同じだ。
フィールドは暗黙的にfinal。 Value Classのフィールドは宣言しなくてもfinalである。インスタンス生成後に状態を変更できない。不変性が言語レベルで保証される。
クラス自体がfinal。 Value Classは継承できない。サブクラスがフィールドを追加したり動作をオーバーライドしたりすると、「同じ値であれば同一」という置換可能性の保証が崩れるためだ。
同期化不可。 synchronized(valueObject)はコンパイルエラーになる。オブジェクトヘッダーに格納されるモニター情報が存在せず、同じ値を持つ二つのインスタンスが論理的に等しいため、特定のインスタンスにロックをかけること自体が意味をなさない。
JDK 28ではnullを許容。 現行バージョンではValue Classの参照はnullになり得る。nullを許容しない型(null-restricted types)は後続のJDKで導入される予定だ。
スカラー化とヒープ平坦化:二つのメモリ最適化
Value Classがパフォーマンスを引き出す方法は二つある。
スカラー化(Scalarization)。 Valueオブジェクトをフィールドに分解してヒープ割り当て自体を排除する。Color(255, 0, 0, 255)を処理する際にJVMはヒープにオブジェクトを生成せず、四つのint値を直接レジスターやスタックに載せてメソッド呼び出しを通じて渡す。エスケープ解析と異なり、このスカラー化は宣言によって保証されるため予測可能だ。
ヒープ平坦化(Heap Flattening)。 配列Color[]にValue Classのインスタンスが格納される場合、JVMは各要素を別個のヒープオブジェクトとして割り当てず、配列内部にデータを直接連続配置する。RGBA各1バイトを32ビットに圧縮エンコードした色の値がメモリ上に順番に並ぶ。走査時にCPUのプリフェッチャーが完全に機能し、キャッシュミスが大幅に減る。ポインター間接参照(pointer indirection)がなくなるのだ。
この二つの最適化が組み合わさることで、データ処理集約型のワークロードにおいて理論上はC言語水準に近いメモリ効率が達成できる。実際のパフォーマンスベンチマークはJDK 28採用後にコミュニティから本格的に出てくるが、JEPドキュメントが示す基準シナリオは意味のある改善を見込んでいる。
Q WorldからL Worldへ:10年間の旅の核心的転換点
2014〜2018年:Q Worldプロトタイプの時代
Project Valhallaが公式化された当初、最初のアプローチは値型のための独立した型システムを作ることだった。内部的に「Q World」と呼ばれたこの設計では、値型はQディスクリプターで示される完全に別種のものだった。参照型と値型は異なる規則に従い、JVMバイトコードレベルでも区別された。
Q Worldプロトタイプはかなりの進捗を見せた。Valhalla専用JDKビルドで直接パフォーマンスを測定でき、その数値は印象的だった。しかし根本的な問題があった。既存のJavaコードとの互換性が極めて複雑になる点だ。ジェネリックコレクションであるList<Point>のような参照型を前提とするコードに値型を入れるには、新しいブリッジメカニズムが必要だった。ジェネリック特殊化(generic specialization)と絡み合い、複雑度が爆発的に増大した。ライブラリエコシステム全体が二つの世界のどちらかを選ばなければならない状況だった。
2019〜2022年:L Worldのブレークスルー
「L World」は根本的な視点の転換だった。名称はJavaオブジェクト参照ディスクリプターのLから来ている。核心的な洞察はこうだ:値型を参照型と別物として扱うのではなく、値型も同じ参照型キャリア(carrier)を通じて流れるようにすれば何が変わるか?
この設計ではValue Classは依然として参照型の一種だ。nullになり得るし、既存のList<Color>のようなジェネリックコンテナにボクシングなしで格納できるよう設計されている。JVMは該当型が値意味論(value semantics)を持つことを知っているため、内部表現を最適化(スカラー化、ヒープ平坦化)できる。外部インターフェースは既存コードと互換性のある参照型でありながら、内部の動作はプリミティブのように効率的だ。
L Worldへの転換は実装の複雑度を急激に下げた。既存のバイトコード生成規則の大部分が維持され、ジェネリックシステムをゼロから再設計する必要がなくなった。エコシステムの互換性がはるかに現実的な範囲に収まった。
名称の変遷:言語設計がいかに難しいか
機能の名前自体も何度か変わった。「value types」→「inline classes」→「value classes」と進化した。各名称変更は単なるマーケティングではなかった。機能の意味論が具体化するにつれ、以前の名称が誤った期待を生む可能性があるという判断からだ。「inline」は実装の詳細を暗示しすぎており、最終的な「value」はユーザーが意識すべき核心属性である「値意味論」を直接表現している。
プレビューとして出荷する理由
JDK 28でValue Classesは--enable-previewフラグなしには有効化されない。コミュニティから「また待つのか」という反応が出ている理由だ。しかしこの選択には明確な根拠がある。
Javaは後方互換性を神聖な契約として扱ってきた。「Write Once, Run Anywhere」の現実的な意味は、1990年代のコードがJDK 28でも動くということだ。この制約のもとで、破壊的変更になり得る機能を一旦プレビューとして公開し、実際のエコシステムからのフィードバックを反映して確定させることがJEP 12で定義された標準手順だ。1,816ファイルにわたる197,000行の変更が、実際の広範なライブラリエコシステムとどう相互作用するかをプレビュー段階で検証するためである。
安定化のスケジュールとしては、JDK 29がLTS(Long-Term Support)リリースとなる2027年9月が現実的な目標として挙げられる。日本のSIerや金融機関が採用判断をLTS基準で行うことを考えると、実質的な導入時期はその時点になる可能性が高い。
実務コードは何が変わるか
パフォーマンスが変わるコードパターン
Value Classesの最も直接的な恩恵を受けるのは、小型データ集約型のコードだ。2D/3DゲームエンジンのVector2、Vector3、Pointのような座標クラス、金融計算のMoney、Rate、Quantityのようなドメイン値オブジェクト、データ処理パイプラインの中間表現などが該当する。数百万個のインスタンスを生成して走査するワークロードで、ヒープ平坦化の効果が際立つ。
日本の金融システムにおいては、為替レートの計算や決済処理のバッチ処理で大量の値オブジェクトを扱うシナリオが多い。これまでプリミティブ配列で手動実装してきた最適化が、Value Classesによって型安全かつ保守性の高い形で実現できる。
JDK自体のInteger、Long、Doubleのようなボクシング型もValue Classへ移行することが目標だ。この変更が完了すれば、既存コードのオートボクシング/アンボクシングのオーバーヘッドが自動的に減る効果がある。
破壊的変更:何が壊れるか
Value Classesは既存の一部の慣習を無効化する。
同期化。 synchronized(someValueObject)パターンはコンパイルエラーになる。Valueオブジェクトはモニターを持たないからだ。このパターンに依存するコードはReentrantLockや他の同期化メカニズムに移行する必要がある。大規模レガシーシステムでは、このパターンが散在している可能性があり、移行コストの事前評価が必要だ。
オブジェクト同一性の検査。 ==で参照の同一性を検査していたコードが予期しない動作をする可能性がある。Value Classでは==が内容を比較するため、同一性を意図したコードは再確認が必要だ。
リフレクションとシリアライズ。 同一性のないオブジェクトを扱う一部のリフレクションベースのフレームワークやシリアライズライブラリは、Value Classのインスタンスを想定通りに処理できない場合がある。日本のエンタープライズ環境で広く使われているJacksonやORM系ライブラリの対応状況を確認することが重要だ。
Kotlin data class、Scala case class、C# structとの比較
技術コミュニティでよく出る疑問は「Kotlinのdata classがすでにこれをやっていないか?」というものだ。核心的な違いはメモリレイアウトにある。Kotlin data classは依然としてJVMの参照型にコンパイルされるため、ヒープ割り当てとポインター間接参照がなくならない。data classはequals()/hashCode()/copy()などを自動生成する言語の便宜機能であり、JVMレベルのメモリ最適化ではない。
C#のstructは機能的にValue Classesに近い。スタック割り当てと値意味論をサポートし、配列では連続したメモリレイアウトを持つ。ただしC#のstructはインターフェース実装時にボクシングが発生するという古い制約があり、コピーコストが明示的でないというトレードオフがある。
JavaのValue Classesは既存の参照型キャリアとの互換性を維持しながらメモリ効率を得るという点で独自のアプローチだ。nullが許容されるという点もC# structとの区別点だ(null制限型は後続JDKで追加予定)。
JDK 28にないもの
現行プレビューで欠けている機能も明確に把握しておく必要がある。Null制限型(Color!のような表記が議論されている)は含まれていない。ジェネリック特殊化(specialized generics、List<int>のようなプリミティブジェネリック)もない。128ビットエンコーディング最適化やベクタリゼーションヒントもまだだ。JDK 28はValue Class宣言構文と基本的なスカラー化/ヒープ平坦化を確立する基盤リリースである。
結論
Project Valhallaが解決しようとしている問題は、Java言語設計における30年来の緊張だ。表現力あるオブジェクト指向とプリミティブレベルの性能効率の間で選択しなければならなかった状況を、Value Classesは「どちらも」という方向で解消する。10年かかったのは単に実装が困難だったからではなく、L Worldへの転換のような根本的な再考が必要だったからだ。
JDK 28プレビューは完成ではなく出発点だ。Null制限型、ジェネリック特殊化、性能検証を含む全体像は2027年のLTSサイクルを通じてエンタープライズ環境に到達するだろう。しかし方向性は確定した。コードをプリミティブのように動かすという約束が、12年を経てビルド可能な形として存在する。
日本のエンタープライズシステムの多くはJavaで動いており、LTSサイクルに沿った慎重な技術評価が行われる。2027年のJDK 29 LTSが採用判断の分水嶺となる可能性が高い。金融、公共、インフラ系システムの担当者にとって今重要なのは、プレビュー機能を試して影響範囲——特に既存の同期化パターンとシリアライズ層——を早期に把握しておくことだ。12年待ったが、残りの1〜2年は準備に使う価値がある。
出典
- JVM Weekly, “Project Valhalla, Explained: How a Decade of Work Arrives in JDK 28” (2026-06-19): https://www.jvm-weekly.com/p/project-valhalla-explained-how-a
- Hacker Newsディスカッションスレッド、656ポイント (2026-06-19): https://news.ycombinator.com/
- OpenJDK Project Valhalla公式ページ: https://openjdk.org/projects/valhalla/
- JEP 401 (Value Classes and Objects): https://openjdk.org/jeps/401
- Brian Goetz, “State of Valhalla” シリーズドキュメント: https://openjdk.org/projects/valhalla/design-notes/