LTS を 17 から 21 に上げたら、アプリが起動しなくなります。
Unrecognized VM option 'UseBiasedLocking'
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
JDK 17 では、まったく同じコマンドラインが動いていました。 警告は出ていましたが、終了コード 0 でした。
直し方は、-XX:+UseBiasedLocking を消すことです。置き換えるフラグはありません。biased locking という機能そのものが JDK 18 で JVM から取り除かれたので、代わりに指定できるものが存在しません。ただし、消すフラグは 1 つとは限りません(後述の「一族」)。
java -cp app.jar App # フラグを消すだけ
LTS だけを渡り歩くと、廃止の段を 1 版も踏まない
このフラグを版ごとに渡すと、扱いが 3 回変わります。
| JDK | -XX:+UseBiasedLocking を渡したときの JVM の出力 | 終了コード | |
|---|---|---|---|
| 11 | (何も言わない。機能は既定で有効) | 0 | LTS |
| 17 | warning: Option UseBiasedLocking was deprecated in version 15.0 and will likely be removed in a future release. | 0 | LTS |
| 18 | warning: Ignoring option UseBiasedLocking; support was removed in 18.0 | 0 | |
| 19 | Unrecognized VM option 'UseBiasedLocking' | 1 | |
| 20 | 同上 | 1 | |
| 21 | 同上 | 1 | LTS |
JDK 17 も、削除されることは告げています——will likely be removed in a future release(将来のどこかで削除されるだろう)。ただしどの版で何が起きるかは書いていません。版を名指しした行(support was removed in 18.0)は JDK 18 でしか出ず、18・19・20 は非 LTS です。
つまり LTS だけを踏んで 17 から 21 へ移ると、「受理はされるが効いていない」という廃止の段を 1 版も踏まないまま、警告つきで起動する状態から起動しない状態へ直接跳びます。17 の警告を CI のログで拾っていれば防げましたが、その警告は「いつか消える」としか言っておらず、起動しなくなる版を教えてくれるのは、通らない道(JDK 18)の上にだけ置かれています。
このエラーは「フラグが消えた」というより、JDK が要らなくなったフラグを扱う 3 つの段の、最後の段です。段は 非推奨(フラグはある・効く・警告する/終了コード 0)→ 廃止(フラグはある・受理される・効かない/終了コード 0)→ 削除(フラグを知らない/終了コード 1)と進みます。この規則は JVM 全体に共通で、Java 8 → 17 で CMS のフラグが拒否される件 や JDK 24 で Security Manager のフラグが拒否される件 も同じ道筋です。
符号を反転しても拒否されます
「有効にするのがだめなら、無効にすればいい」は通りません。
java -XX:-UseBiasedLocking -cp app.jar App
# → Unrecognized VM option 'UseBiasedLocking'(終了コード 1)
JVM は UseBiasedLocking という名前そのものを知りません。+ か - かは、名前を認識したあとの話です。フラグは消すしかありません。
消したら性能は落ちるのか
このフラグを JAVA_OPTS に書いていたということは、性能上の理由があったはずです。答えを分けて書きます。
JDK 19 以降で「消す」ことによる損失はありません。 機能は JDK 18 で JVM から取り除かれていて、19 以降にはそもそも存在しないからです。JDK 18 で -XX:+UseBiasedLocking を渡しても、JVM は Ignoring option ... と言って無視して起動します。フラグの一覧を出しても、この名前はもう出てきません。
$ java -XX:+UseBiasedLocking -XX:+PrintFlagsFinal -version # JDK 18
OpenJDK 64-Bit Server VM warning: Ignoring option UseBiasedLocking; support was removed in 18.0
(一覧に UseBiasedLocking の行は無い)
つまり、同じ JDK 21 上では、フラグを消しても残しても(残せば起動しませんが)動作は変わりません。消すことで何かを失うわけではありません。
失われるのは、JDK 17 との比較です。 biased locking は JDK 17 では実際に機能していました。ただし、JDK 15 の時点で既定が無効に変わっています。フラグの既定値を版ごとに確認すると、こうです。
| JDK | UseBiasedLocking の既定値(実測) |
|---|---|
| 8 | true |
| 11 | true |
| 17 | false |
JDK 17 でこのフラグを明示的に書いていたということは、既定で無効になったものを、手で有効に戻していたことになります。その状態から 21 に上げると、フラグを足して以前の挙動に戻す手段はありません。17 のころと同等のロック最適化は得られなくなります。
差が出るコードの形は、OpenJDK の設計文書(JEP 374)が述べています——biased locking は「同じスレッドが同じロックを繰り返し取る」場合の最適化で、Hashtable・Vector・StringBuffer のように同期が組み込まれた古い型を単一スレッドから多用するコードに効きました。逆に、複数スレッドが同じロックを奪い合う形では、biased locking の取り消し処理そのものが費用になります。当プロジェクトでは性能を実測していません。 実際にどれだけ影響があるかは、21 に上げたうえで自分のワークロードで測ることになります。
UseBiasedLocking を消しても終わらない:一族のフラグ
biased locking を明示的に有効化していたなら、その調整フラグも JAVA_OPTS に同居していることがあります。JEP 374 は関連フラグをまとめて非推奨にし、JDK 18 でまとめて廃止しました。JDK 21 に単体で渡すと、こうなります。
| JDK 21 に渡すフラグ | 結果 |
|---|---|
-XX:BiasedLockingStartupDelay=0 | Unrecognized VM option 'BiasedLockingStartupDelay=0' |
-XX:BiasedLockingBulkRebiasThreshold=20 | Unrecognized VM option 'BiasedLockingBulkRebiasThreshold=20' |
-XX:+UseOptoBiasInlining | Unrecognized VM option 'UseOptoBiasInlining' |
これらも UseBiasedLocking と同じ 3 段をたどります。-XX:BiasedLockingStartupDelay=0(起動直後からバイアスを効かせる定番指定)を例に取ると、JDK 17 は非推奨の警告つきで起動し、JDK 18 は Ignoring option ...; support was removed in 18.0 で起動し、JDK 19 以降は起動を拒否します(実測)。
JVM は知らないオプションを 1 つ見つけた時点で止まるので、UseBiasedLocking を消して再実行すると、次はこちらが報告されます。JAVA_OPTS を開いて BiasedLocking・BiasOpto を含む行をまとめて洗い出してください。
同じ 3 段は、JDK 24 → 25 でも起きています
この 3 段は biased locking に固有のものではありません。HotSpot は要らなくなったフラグをリリースごとにまとめて整理するので、同じ道筋が別の版でも繰り返されます。
JDK 24 で廃止され、JDK 25(LTS)で削除されたフラグの例です(実測)。
| フラグ | JDK 23 | JDK 24 | JDK 25 |
|---|---|---|---|
PreserveAllAnnotations | 非推奨の警告つきで起動 | Ignoring option ...; support was removed in 24.0 | Unrecognized VM option(起動拒否) |
DontYieldALot | 同上 | 同上 | 同上 |
UseEmptySlotsInSupers | 同上 | 同上 | 同上 |
UseNotificationThread | 同上 | 同上 | 同上 |
17 や 21 から 25 へ上げると、23 の警告も 24 の通知も 1 度も見ません。 構図は UseBiasedLocking とまったく同じで、版だけが違います。
「廃止」で消えるのは、機能ではなく機能を切る手段のことがある
ここには UseBiasedLocking には無かった論点があります。上の 4 つのうち UseEmptySlotsInSupers と UseNotificationThread は、既定値が true です(JDK 23 の -XX:+PrintFlagsFinal で確認)。
既定 true のフラグが廃止されるとき、JVM から消えるのは機能ではなく、その機能を切る手段です。実測するとこうなります。
$ java -XX:-UseNotificationThread ... # 無効化しようとする
JDK 23 → warning: Option UseNotificationThread was deprecated ...(終了コード 0・無効化は効く)
JDK 24 → warning: Ignoring option UseNotificationThread; support was removed in 24.0(終了コード 0・無効化が効かない)
JDK 25 → Unrecognized VM option 'UseNotificationThread'(終了コード 1)
-XX:-... で無効化していた場合、挙動が変わったのは JDK 24 の時点です。 24 は終了コード 0 のまま起動し、無効化の指定だけが無視されて、機能は既定の有効側へ戻ります。この変化は警告 1 行以外に何も報告しません。 25 の起動拒否は終了コード 1 で必ず気づきますが、24 の挙動変化は、警告を読んでいなければ何も起きていないように見えます。
25 でフラグを消すこと自体に損失はありません(24 以降の挙動は、消しても消さなくても同じです)。変わったのは 24 のほうです。
フラグを消せないとき
共有のベースイメージや、プラットフォームが挿している環境変数が出どころで、自分では消せないことがあります。JVM に「知らないオプションは捨てて起動しろ」と指示できます。
java -XX:+IgnoreUnrecognizedVMOptions -XX:+UseBiasedLocking -cp app.jar App
# → 起動する(終了コード 0)
JDK 21 で実際に起動します(実測)。ただしこれは起動を通すための指定であって、biased locking が戻るわけではありません(機能は 18 で無くなっています)。そして、そのフラグだけでなく未知のオプション全部を黙って捨てるので、頼っているフラグを打ち間違えても気づけなくなります。出どころを直すまでの足場に留めてください。
コマンドラインに書いていないのにこのエラーが出るなら、フラグは環境変数から入っています。JAVA_TOOL_OPTIONS・_JAVA_OPTIONS・JDK_JAVA_OPTIONS は、拾ったときに Picked up ... という行を出して名乗ります。出どころの一覧は CMS の記事の「フラグはどこから来ているのか」にあります。
切り分け(うまくいかないとき)
- JDK 17 では警告が出るだけで動く:想定どおりです。17 は
非推奨の段で、フラグはまだ効きます。ただし警告が出ている時点で、21 では起動しません。17 のうちに消しておけば、21 への移行でこのエラーには当たりません。 消しても 17 の既定は無効なので、17 上での動作は変わります(上の節のとおりです)。 Ignoring option UseBiasedLocking; support was removed in 18.0と出るが起動する:JDK 18 です。廃止の段で、受理はされますが効いていません。この状態のまま 19 以降に上げると起動しなくなります。- フラグを消したら、別の名前で同じエラーが出た:まず biased locking 一族(
BiasedLockingStartupDelayなど。上の節)を疑ってください。CMS 一族ではありません——CMS のフラグは JDK 15 で削除されているので、JDK 17 で起動できていた構成に CMS フラグは残っていません。もし JDK 8 から一気に上げているなら話は別で、その場合は CMS の記事 が対象です。 mvnやgradle自身が同じエラーで落ちる:JAVA_TOOL_OPTIONSか_JAVA_OPTIONSにフラグが入っています。これらはjavacにも効くので、コンパイルの手前で落ちます(実測)。- 性能が落ちたか確かめたい:21 に上げた状態で測ります。フラグを付け外しして比較することはできません(付けると起動しないため)。比較するなら、JDK 17(フラグあり)と JDK 21 の間で測ることになります。
検証環境
eclipse-temurin@sha256:1eeacc8c...(JDK 21.0.11 LTS)、ネットワーク不要- 再現:
java -XX:+UseBiasedLocking -cp out AppがUnrecognized VM option 'UseBiasedLocking'で終了コード 1(main()に到達せず、APP_STARTEDが出ない) - 修正:
java -cp out Appが終了コード 0(Java のソースは再現・修正で同一)
再現から修正までは errfix の検証ハーネスが機械的に確認しています。本文の版ごとの表(11 は無言/17 は deprecated in version 15.0 の警告つきで起動/18 は Ignoring option ...; support was removed in 18.0 で起動/19・20・21 は Unrecognized VM option で起動拒否)、-XX:-UseBiasedLocking も同じく拒否されること、JDK 18 では -XX:+PrintFlagsFinal の一覧にこのフラグが現れないこと、既定値が JDK 8・11 で true・JDK 17 で false であること、一族のフラグ(BiasedLockingStartupDelay / BiasedLockingBulkRebiasThreshold / UseOptoBiasInlining)も JDK 21 で拒否され、BiasedLockingStartupDelay が 17=警告つき起動/18=Ignoring option/19=拒否と同じ 3 段をたどること、-XX:+IgnoreUnrecognizedVMOptions を足すと JDK 21 でも起動すること、_JAVA_OPTIONS が javac も落とすことは、いずれも Eclipse Temurin の JDK 8・11・17・18・19・20・21 で実測しています。
JDK 24 → 25 の節(PreserveAllAnnotations / DontYieldALot / UseEmptySlotsInSupers / UseNotificationThread)は、eclipse-temurin の 17.0.19・21.0.11・22.0.2・23.0.2・24.0.2・25.0.3(いずれも digest 固定)で実測しています。4 つとも 23=非推奨警告・24=Ignoring option ...; support was removed in 24.0・25=Unrecognized VM option で、UseEmptySlotsInSupers と UseNotificationThread の既定値が true であることは JDK 23 の -XX:+PrintFlagsFinal で確認しました(この 4 つは JDK 24 では一覧から消えます)。-XX:-UseNotificationThread(無効化側)が 23 では受理され、24 では Ignoring option になることも実測しています。
biased locking の既定無効化と関連フラグの非推奨化が JDK 15(JEP 374)であること、機能の削除が JDK 18 であること、どのようなコードで効く最適化だったかは、OpenJDK の一次情報に拠ります。性能そのものは実測していません。