Help, My Java Object Vanished (and the GC Is Not at Fault)

2025/11/29 22:47

Help, My Java Object Vanished (and the GC Is Not at Fault)

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

## Summary

この記事では、HotSpot が JEP 450 Compact Object Headers をサポートするために更新された際、特に markWord 配列内の value‑object ビットを位置 3 から位置 7 に移動したことが、複数のアーキテクチャで約 75 回の間欠的失敗を引き起こした経緯を説明しています。エラーには単体テストのクラッシュ、Java の `AssertionError`、および `NoClassDefFoundError` メッセージが含まれます。

初期デバッグは静的プロトタイプ定数に焦点を当てましたが、未使用の static‑prototype ロジックを削除しても失敗は解消されませんでした。問題を再現するには Compact Object Headers(`-XX:-TieredCompilation`, `-XX:+UseCompactObjectHeaders` をオフ)を無効にしつつ、C2 JIT コンパイルを有効にする必要がありました。システマティックなテストケースの削減(例:`MultiReleaseJarProperties` テストの分離)や jtreg の再実行コマンドでも決定的な失敗は得られませんでした。

著者は次に自動化されたメソッドレベルでの隔離(`-XX:CompileCommand=compileonly`)を使用し、疑わしい箇所として `java.util.concurrent.ConcurrentHashMap::get` を特定しました。調査によって、C2 の `Object::hashCode` 用イントリンシックが誤ってコンパイルされていたことが判明しました。具体的には、`UseObjectMonitorTable` が無効化された際に、ObjectMonitor 監視拡張コード内の不正なビットマスクがメタデータビットではなくネイティブポインタ上で操作されていたためです。

Compact Object Headers は通常 `UseObjectMonitorTable` を true に設定し、このエラーを隠蔽します。無効化するとバグが露呈します。不正なマスクにより、軽量ロッキングのスロー・パスがスキップされ、ポインタビットがモニター値と一致した場合に null オブジェクトやクラスロード失敗が発生しました。以前はネイティブ 16 バイトメモリアラインメントが特定のビットをゼロに保つことで問題を隠していました。

修正では、マスク `markWord::inline_type_mask_in_place` を `markWord::lock_mask_in_place` に置き換え、正しい動作を回復しました。著者はまた、初期試行が無駄に見える場合でも、ターゲットを絞った質問と利用可能なツールを活用する構造化されたデバッグ手法の重要性を強調しています。

本文

本日は、OpenJDKプロジェクトでHotSpot Java Virtual Machine(JVM)の開発者として経験した最近の旅についてお話しします。新機能のテストを実行している最中に、Javaオブジェクトやクラスが無作為に消えてしまったことに気づきました!その後に起こった出来事は、私の人生(今まで)の中で最も興味深いデバッグと修正体験だったと思います(これから世界と共有したい)。

この記事は広範な(コンピュータサイエンス)読者を対象としています。JavaやJVMに関する知識は必須ではありませんが、低レベルプログラミングへの少しの好奇心は歓迎です。

執筆意図は次の通りです:

  • Project Valhalla と値オブジェクト(value object)を紹介
  • HotSpot の内部動作に洞察を与え、貢献したくなるよう促す
  • JVM フラグが開発者にとってどのように役立つか実践的に示す
  • デバッグで得た教訓を共有
  • 将来の自分や同僚のためにプロセスを文書化
  • 成果について語り、ASCII アートも添える

多くを書きました。各章の要約(結論は除く)を入れています。全体を読む価値があると確信していますので、ご興味のある部分だけでもご活用ください。


はじめに

何をしていたかというと?
JEP 450 が定める Project Valhalla 用フォーマットに合わせて markWord を変更していました。以下は専門用語を分解したものです。

Java 101

Java は汎用途プログラミング言語です。プラットフォーム非依存のバイトコードへコンパイルされ、Java Virtual Machine(HotSpot)が実行します。JVM は自動ガベージコレクションを行い、JIT コンパイラで頻繁に呼ばれるメソッドをネイティブコードへ変換して高速化します。

オブジェクトはヒープ上に配置され、各オブジェクトにはヘッダーにメタデータが格納されています。ここでは「ビット」が特に興味深いです。

従来のオブジェクトヘッダーは markWord と class Word(クラスポインタ)で構成され、64‑bit アーキテクチャでは 96〜128 ビットでした。JDK 24 から JEP 450:Compact Object Headers¹ が導入され、class Word を markWord に統合することでヘッダーサイズを 64 ビットに縮小しました。

markWord の情報は以下のようになります(64‑bit システム)²:

   7   3  0
VVVVAAAASTT
          ^^----- tag bits
         ^------- self‑forwarding bit (GC)
     ^^^^-------- age bits (GC)
 ^^^^------------ Valhalla‑reserved bits
  • TT – ロック用タグビット。01 はロックされていない、00 と 10 は軽量ロックとモニタロック。
  • S – GC が使用するセルフフォワーディングビット(ここでは無視)。
  • AAAA – 世代別ガベージコレクションのオブジェクト年齢追跡用ビット。
  • VVVV – Valhalla 用に予約された 4 ビット(実装上は
    unused_gap_bits
    と呼ばれる)。

Project Valhalla

Project Valhalla は「Java のエピックリファクタ」とも呼ばれ、数多くの機能セットが開発中です。ここで関係するのは JEP 401:Value Classes and Objects です。値オブジェクトはフィールドだけで区別され、ヒープフラッテン化⁴ やスカラー化⁵ といった最適化を可能にします。「通常クラス/オブジェクト」は identity クラス/オブジェクトと呼ばれます。

Valhalla は JDK のフォークです。主流の変更は頻繁に取り込むものの、自然と遅延が生じます。Compact Object Headers が Valhalla にマージされた際、11 ビットのレイアウトを少し変更して統合しました。選択した形式は次の通り(JEP 450 と対比):

   7   3  0
VVVVAAAASTT  <- JEP 450
VVVAAAAVSTT  <- Valhalla
        ^------- value object ビットに注目

Valhalla のレイアウトでは、最下位の V が age bits より下に位置します。ビット 36(0‑indexed)にある V は「値オブジェクトであるか」を示すフラグです。このビットが必要なのは、HotSpot が値オブジェクトと identity オブジェクトで多くの処理を分岐させるためです。

私の変更

私の変更は簡潔でした:11 ビット(

VVVAAAAVSTT
)を JEP 450 の
VVVVAAAASTT
に合わせて更新するだけ。つまり、値オブジェクトビットを上げ(age bits を下げる)ました。


要約

markWord はオブジェクトヘッダーの一部で、Java オブジェクトのメタデータを保持します。Project Valhalla ではフィールドだけで区別される値オブジェクトが導入され、その存在はヘッダー内のビットで示されます。Valhalla ではこのビットはインデックス 3 にあり、Compact Object Headers(JEP 450)に準拠するためにはインデックス 7 に移動させる必要がありました。


広範な失敗

変更は単純でしたが、いくつかのアーキテクチャ・プラットフォームで 75 通りのテスト失敗を引き起こしました。問題点は:

  • 広範 – VM の外側にある多くのコンポーネントが影響
  • 断続的 – 時には成功し、時には失敗(再現性が低い)
  • 明示的でない – ほとんどのアサーションは失敗せず、アプリケーションレベルでエラーが発生

HotSpot のコードベースは約 550 KLoC⁷ と極めて複雑です。再現可能なクラッシュを特定することは非常に重要でした。

症状 A – 単体テストの失敗

GoogleTest フレームワークで書かれた単体テストの 4 つが失敗しました:

[  FAILED  ] 4 tests
[  FAILED  ] markWord.inline_type_prototype_vm
[  FAILED  ] markWord.null_free_flat_array_prototype_vm
[  FAILED  ] markWord.nullable_flat_array_prototype_vm
[  FAILED  ] markWord.null_free_array_prototype_vm

症状 B –
java.lang.AssertionError

いくつかのテスト(例:

java/util/jar/JarFile/mrjar/MultiReleaseJarProperties.java
)が TestNG ハーネスで AssertionError を投げました:

java.lang.AssertionError: l should not be null
    at org.testng.ClassMethodMap.removeAndCheckIfLast(ClassMethodMap.java:55)
    …

ソースコード自体が

l
のヌルチェックを行っているため、消えてしまうのは奇妙です。

症状 C –
java.lang.NoClassDefFoundError

sun/security/krb5
内のいくつかのテスト(例:
sun/security/krb5/etype/UnsupportedKeyType.java
)が NoClassDefFoundError で失敗。実際に見つからなかったクラスはテストや同じテストを複数回走らせると変わり、典型的な断続性を示します。

java.lang.NoClassDefFoundError: sun/security/krb5/internal/Krb5
    at java.base/jdk.internal.loader.NativeLibraries.load(Native Method)
    …

Act I:意味上の困難

最も手軽に解決できたのは単体テストの失敗でした。C++ で書かれ、テスト対象関数を直接呼び出せるためです。

Valhalla 固有のマクロ(

EnableValhalla
)が原因で、静的プロトタイプに関連する定数が未使用・レガシーだったため削除しました。単体テストは通過しましたが、アプリケーションレベルの失敗は残りました。


Act II:縮小・再現・リサイクル

HotSpot フラグを試し、C2 JIT コンパイラと Compact Object Headers が無効な状態で問題が発生することを突き止めました。テストケースを大幅に最小化しましたが、さらに細分化できず、ミスコンパイルの可能性が高いと推測しました。


Act III:コンパイラ解析

-XX:+PrintCompilation
で全てのコンパイル済みメソッドを列挙し、
-XX:CompileCommand
を使ってそれらを走査するスクリプトを書きました。結果として
ConcurrentHashMap::get
が疑わしい対象でした。インライン展開を調べると、C2 の
Object::hashCode
イントリンが原因であることが判明しました。


Act IV:バグ

イントリンのソースは次のような分岐を含みます:

if (!UseObjectMonitorTable) { … }

Compact Object Headers では

UseObjectMonitorTable
が true に設定されるため、問題は発生しませんでした。分岐内でビットマスク(
markWord::inline_type_mask_in_place
)が値オブジェクトビットの移動後に誤って構築されていました。

オブジェクトモニタが膨張すると、markWord はネイティブポインタになり、ロックビットだけが設定されます。間違ったマスクでポインタをマスクすると、スローウィーガードで偽陰性となり、同期が失敗し、結果としてヌルオブジェクトや NoClassDefFoundError が発生しました。

修正は

markWord::lock_mask_in_place
を使用するだけでした。パッチを適用すると全ての症状が消えました。


Act V:事後分析

なぜ VM は私の変更前に影響しなかったか?
64‑bit ネイティブポインタは

malloc
で取得されると 16 バイトアラインメントになるため、下位 4 ビットは常にゼロです(うち 2 ビットがロックメタデータによって上書きされる)。誤ったマスクはこれらの低ビットの一つしか確認しませんでした。値オブジェクトビットを移動すると、バグが顕在化しました。


閉じに & まとめ

  • 堅実なメソッドと仮定への挑戦 – HotSpot のような大規模コードベースでは、デバッグ進行のために論理的思考が不可欠です。
  • 適切なタイミングで正しい質問をする – 何・いつ尋ねるか、誰に聞くかを知りましょう。助けを求めることは恥ずべきことではありません。
  • ツールを活用せよ
    lldb
    を使えなくても、基本的なデバッギングツールに慣れておくと非常に有益です。

同僚の皆さんに感謝しつつ、今回の経験が他者にも役立てば幸いです。ありがとうございました!

同じ日のほかのニュース

一覧に戻る →

2025/12/05 10:05

BMW PHEV: Safety fuse replacement is extremely expensive

## Japanese Translation: --- ## 要約 BMW の高電圧バッテリー安全ヒューズ(1件あたり約 €5 000)は、iBMUCP モジュール全体(約 €1 100+税)を交換する必要があります。iBMUCP は溶接で閉じられ、暗号的にロックされており、サービス前後に車両全体のフラッシュが必須です。ISTA で承認された手順を試みると、盗難防止ロックを作動させてモジュールを消去し、新しいバッテリーパック(約 €6 000+VAT)が必要になるリスクがあります。 BMW は公式 ISTA 診断へのアクセスをブロックし、オーストリアでのワークショップ認定要求を却下しているため、独立した修理はさらに困難です。対照的に、テスラのプロファイザー/ BMS リセットは €11–€50 だけです。 21F2A8 や 21F35B といったエラーコードは高電圧安全問題を示しています。OEM サービスは €4 000+税と見積もられ、iBMUCP の交換作業には通常 24–50 時間がかかり、ツール費用は €25 000 を超えます(ICOM、IMIB、AOS)。 著者のチームは iBMUCP 内にある Infineon TC375 MCU 上の JTAG/DAP 保護を突破する計画です。成功すれば回復が簡素化され、作業時間とツール費用が削減され、不必要なバッテリー交換が減り、自動車修理における CO₂ 排出量も低減します。 この作業を提供しているサービスセンターは、ザグレブ、ベルリン、スロベニア、およびセルビア(EV CLINIC)に所在しています。

2025/12/05 9:03

Trick users and bypass warnings – Modern SVG Clickjacking attacks

## Japanese Translation: ## Summary 本論文は、すべてのSVG `<fe*>` フィルタプリミティブをチェーン化することでチューリング完全なツールセットを構築できることを示し、高度なクリックジャッキングやクロスオリジンデータ外部流出攻撃を可能にします。 - 著者はまず、`feColorMatrix` と `feDisplacementMap` を用いて CSS/SVG で Apple の Liquid Glass 効果を再現し、クロスオリジン iframe 上でも動作することを証明しました。 - プリミティブ(切り取り・タイル化・算術合成・カラー行列変換・ガウシアンぼかし)を組み合わせて、外国オリジンから画像データを読み取るピクセル読取回路を構築しました。 - `feBlend` と `feComposite` を用いて NOT, AND, OR, XOR, NAND, NOR, XNOR の論理ゲートを実装し、SVG フィルタ内で任意のブール演算が可能になりました。 - これらのゲートを利用して、ダイアログ表示状態・読み込み状態・チェックボックス状態・赤文字検出に応じて画像を条件付きで表示するマルチステップクリックジャッキングシナリオ(「Securify」)を構築しました。 - この手法は Google Docs に適用され、攻撃者が「Generate Document」→ CAPTCHA 入力 → 提案選択 → ボタン押下 → 読み込み画面という一連の操作を SVG フィルタ内で自動化します。 - SVG フィルタ内(`feDisplacementMap` と Reed–Solomon エラー訂正表を使用)に QR コードジェネレータを構築し、生成されたコードは外部流出データをエンコードしており、ユーザーにスキャンさせて攻撃者のサーバへ送信されます。 クリックハイジャックや SVG フィルタタイミング攻撃に関する既存研究では、マルチステップロジックとクロスオリジンデータ読取を組み合わせたものはなく、本論文は新規かつ自動化された攻撃ベクターを提示しています。 調査結果は、攻撃者が他のウェブサービスに対して同様のシーケンスを自動化し、マルウェアやフィッシングキャンペーンに埋め込む可能性があることを示唆しています。ユーザーは意図しないクリックとデータ漏洩のリスクにさらされ、サードパーティ iframe を組み込む企業は侵害リスクが高まります。本研究は、ウェブエコシステム全体で SVG フィルタ使用時のオリジン境界保護を強化する必要性を訴えています。

2025/12/05 10:15

NeurIPS 2025 Best Paper Awards

## Japanese Translation: > **概要:** > 本会議の論文賞授与式では、プログラムチェアとデータベース&ベンチマークトラックチェアが指名した委員会により選ばれた優秀な7件の論文が表彰されました。最終承認は総合チェア、新世代チェア、およびアクセシビリティチェアから行われました。賞には、Datasets & Benchmarkトラックから1件を含む4件のベストペーパーと3件のレナーアップが含まれます。 > > *ベストペーパー*: > 1. **「Artificial Hivemind: The Open‑Ended Homogeneity of Language Models」** – Infinity‑Chat(26 Kクエリ、31 K人間アノテーション)とオープンエンドプロンプトの分類法を紹介し、モデル内部での反復とモデル間での均質性を明らかにします。 > 2. **「Gated Attention for Large Language Models」** – SDPA後にヘッド特異的シグモイドゲートを追加すると性能・安定性が向上し、密結合およびMoEトランスフォーマーの注意サンク問題を緩和することを示します。コードはGitHub/HuggingFaceで公開されています。 > 3. **「1000 Layer Networks for Self‑Supervised RL」** – 1024層という深さが目標条件付きタスクで性能を向上させ、他のベースラインを上回ることを実証します。 > 4. **「Why Diffusion Models Don’t Memorize」** – 2つの時間スケール(初期一般化と後期記憶)を特定し、暗黙的な動的正則化が過学習を防ぐことを示します。 > > *レナーアップ*: > - 「Does Reinforcement Learning Really Incentivize Reasoning Capacity in LLMs Beyond the Base Model?」 – RLVRはサンプリング効率を改善するものの、推論能力を拡張しない;蒸留により新たなパターンが導入される可能性があります。 > - 「Optimal Mistake Bounds for Transductive Online Learning」 – Ω(√d) の下限と O(√d) の上限を証明し、以前の結果を指数関数的に改善し、転移学習と標準オンライン学習との間に二次的ギャップがあることを示します。 > - 「Superposition Yields Robust Neural Scaling」 – 表現の重ね合わせがニューラルスケーリング法則を駆動することを示し、強い重ね合わせは多様な周波数分布にわたってモデル次元に逆比例して損失を縮小します。 > > これらの賞は拡散理論、自律学習RL、LLMの注意メカニズム、LLMにおける推論、オンライン学習理論、ニューラルスケーリング法則、および言語モデル多様性のベンチマーク手法を網羅しています。これらは会議が実証的ブレイクスルーと理論的洞察の両方にコミットしていることを示しています。 > > この改訂版概要は、Key Points List のすべての主要ポイントを忠実に列挙し、裏付けのない推測を避け、読み手に優しい明確なオーバービューを提供します。