Error [Unrecognized VM option 'UseConcMarkSweepGC']

`Unrecognized VM option 'UseConcMarkSweepGC'` の直し方(Java 8 から 17 に上げたら JVM が起動しない)

FIX SUMMARY verified
Applies when
eclipse-temurin@sha256:89dc1a6e09920ea26b2ede6fddfcac1a7508b50159a6d04c918a46132953aab6-XX:+UseConcMarkSweepGC (and the CMS/ParNew/Perm companion flags) carried in JAVA_OPTS since Java 8; CMS was obsoleted in JDK 14 and the flag name is gone from JDK 15 on, so the VM refuses to start before main()

Verified: reproduced in eclipse-temurin@sha256:89dc1a6e09920ea26b2ede6fddfcac1a7508b50159a6d04c918a46132953aab6, then the Unrecognized VM option 'UseConcMarkSweepGC' signature was gone after the fix (exit 0).

Java 8 で動いていたアプリを JDK 17 で動かすと、起動した瞬間に終わります。

Unrecognized VM option 'UseConcMarkSweepGC'
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

アプリのログは 1 行も出ません。落ちているのは JVM の起動処理で、main() はまだ呼ばれていません。 アプリのコードもクラスパスも、この時点では一度も読まれていません。

原因は -XX:+UseConcMarkSweepGC(CMS の指定)です。CMS collector 本体は JDK 14 で削除されましたJEP 363)。互換のため、JDK 14 では -XX:+UseConcMarkSweepGC というフラグだけは受理して黙殺していましたが、JDK 15 でフラグ名も消えました。JVM は知らないオプションを受け取ると、起動を拒否します。

まず起動させるだけなら、JDK 17 で有効な GC 指定に置き換えます。

java -XX:+UseG1GC -cp app.jar App

UseConcMarkSweepGC を消しても、次のフラグで同じエラーが出る

このエラーへの最短の対応は「そのフラグを消す」ですが、たいてい 1 回では終わりません。JVM は知らないオプションを 1 つ見つけた時点で止まるので、報告されるのは常に最初の 1 つです。CMS を使っていた JAVA_OPTS には、CMS を調整するフラグが並んでいます。JDK 17 でそれぞれ単体で試すと、こうなります。

JDK 17 に渡すフラグ結果
-XX:+UseConcMarkSweepGCUnrecognized VM option 'UseConcMarkSweepGC'
-XX:+UseParNewGCUnrecognized VM option 'UseParNewGC'
-XX:CMSInitiatingOccupancyFraction=70Unrecognized VM option 'CMSInitiatingOccupancyFraction=70'
-XX:+UseCMSInitiatingOccupancyOnlyUnrecognized VM option 'UseCMSInitiatingOccupancyOnly'
-XX:+CMSParallelRemarkEnabledUnrecognized VM option 'CMSParallelRemarkEnabled'
-XX:MaxPermSize=256mUnrecognized VM option 'MaxPermSize=256m'
-XX:PermSize=128mUnrecognized VM option 'PermSize=128m'

さらに、CMS という文字列を含まないのに同じ形で落ちるものがあります。

JDK 17 に渡すフラグ結果
-XX:+CMSClassUnloadingEnabledUnrecognized VM option 'CMSClassUnloadingEnabled'
-XX:+CMSScavengeBeforeRemarkUnrecognized VM option 'CMSScavengeBeforeRemark'
-XX:+UseCMSCompactAtFullCollectionUnrecognized VM option 'UseCMSCompactAtFullCollection'
-XX:+UseParallelOldGCUnrecognized VM option 'UseParallelOldGC'

UseParallelOldGCCMSParNewPerm も含みません。 Java 8 世代の JAVA_OPTS には CMS と同じくらい普通に載っていて、JDK 16 で削除されています。

すべて同じ形で起動を拒否します。 1 つ消して再実行、また 1 つ消して再実行、を繰り返すことになるので、最初から JAVA_OPTS(や Dockerfilesystemd の unit、Kubernetes の manifest)を開いて、CMSParNewPermParallelOld を含むフラグをまとめて洗い出すほうが早く終わります。

上の 2 つの表は実測した代表例であって、網羅表ではありませんCMSIncrementalMode など、ここに無い CMS 系フラグもあります)。一覧を暗記するのではなく、設定ファイルを開いて上の 4 語で洗い出すほうが確実です。

MaxPermSize は、JDK 8 の時点ですでに効いていなかった

上の表に MaxPermSize が混ざっているのは偶然ではありません。同じフラグを JDK 8 に渡すと、こうなります。

OpenJDK 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0
APP_STARTED

警告を出して、無視して、起動します(終了コード 0)。 PermGen は Java 8 で無くなっているので、このフラグは Java 8 の時点で何もしていません。それでもアプリは動き、終了コードは 0 のままなので、設定から外す理由が生まれません。そのまま JAVA_OPTS に乗り続けて、JDK 17 で致命傷になります。

JDK が要らなくなったフラグを扱う段は 3 つあり、同じ JAVA_OPTS の中で、フラグごとに段がばらばらです。

JVM のふるまい終了コード
非推奨フラグはある。効く。警告を出す0
廃止フラグはある。受理する。効かないIgnoring option ...0
削除フラグを知らないUnrecognized VM option1

UseConcMarkSweepGC は、JDK 8 で無言 → JDK 11 で非推奨(Option UseConcMarkSweepGC was deprecated in version 9.0 ...)→ JDK 14 で廃止 → JDK 15 以降で削除、と進みました。8 から 17 へ一気に上げると、警告が出る版(11)も、無視される版(14)も踏みません。 予告なしに最後の段に着地します。この 3 段は JVM 全体の規則で、JDK 24 で Security Manager のフラグが起動を拒否する件 や JDK 21 で UseBiasedLocking が拒否される件 も同じ道筋です。

直し方:消すのではなく、置き換える

フラグを消すだけでも JDK 17 は起動します。 ただし、そのときどの GC が選ばれるかは実行環境が決めます。JVM は「2 プロセッサ以上かつ 1792 MB 以上」を server-class と見なし、満たせば G1、満たさなければ Serial を選びます(Oracle GC Tuning Guide — Ergonomics)。実測すると、境界はそのとおりに出ます。

実行環境JDK 17 が選ぶ GC
16 CPU / 16 GB のホストUseG1GC = true {ergonomic}
--cpus=2 --memory=2gUseG1GC = true {ergonomic}
--cpus=1 --memory=512mUseSerialGC = true {ergonomic}

1 CPU に絞ったジョブや、512 MB 級の小さいコンテナは Serial 側に落ちます。 停止時間を気にして CMS を選んでいたアプリが、フラグを消しただけで単一スレッドの Serial GC で動き出す、ということが起こります。しかも何のエラーも警告も出ません。逆に、2 CPU・2 GB を割り当てた Pod や一般的な CI ランナーは server-class の側なので、G1 が選ばれます。「小さいから Serial」と決め打たず、java -XX:+PrintFlagsFinal -version で自分の環境を見てください。

だから置き換えます。-XX:+UseConcMarkSweepGC は「GC を選んだ」という運用の判断であって、消せばその判断ごと消え、あとは入れ物任せになります。

java -XX:+UseG1GC -cp app.jar App     # 明示的に G1 を選ぶ

どの GC に置き換えるかは、CMS を選んだ理由で決まります。 CMS は停止時間を短くするための GC でした。同じ動機なら、まず G1(停止時間と処理量の折衷)です。停止時間をさらに詰めるなら ZGC(-XX:+UseZGC)がありますが、ZGC はメモリの使い方が G1 と違うので、コンテナに割り当てた上限が小さいと当てが外れます——切り替えるなら、その環境で測ってからにしてください。処理量だけが要るなら Parallel(-XX:+UseParallelGC)もあります。

CMS の調整値(CMSInitiatingOccupancyFraction など)は移植できません——調整対象の GC そのものが無いからです。停止時間の目標を持っているなら、G1 では -XX:MaxGCPauseMillis に読み替えることになります。当プロジェクトが実測したのは「その指定で JVM が起動すること」だけで、性能は測っていません。

なお、古い GC フラグが全部落ちるわけではありません。 たとえば -XX:+PrintGCDetails は JDK 17 でも受理されます。

[warning][gc] -XX:+PrintGCDetails is deprecated. Will use -Xlog:gc* instead.
[info][gc] Using G1
APP_STARTED

非推奨の警告を出したうえで、新しい -Xlog:gc* に読み替えて動きます(終了コード 0)。落ちるのは「削除された」フラグだけです。

フラグを消せないとき:IgnoreUnrecognizedVMOptions

共有のベースイメージや、プラットフォームが挿している環境変数が出どころで、自分では消せないことがあります。その場合、JVM に「知らないオプションは捨てて起動しろ」と指示できます。

java -XX:+IgnoreUnrecognizedVMOptions -XX:+UseConcMarkSweepGC -cp app.jar App
# → APP_STARTED(終了コード 0)

JDK 17 で実際に起動します(実測)。ただしこれは、そのフラグだけでなく、未知のオプション全部に効きます。頼っているフラグを打ち間違えても、同じように黙って捨てられます。起動を通すための一時的な足場として使い、出どころを直すまでの間に留めてください。

フラグはどこから来ているのか

コマンドラインに書いていないのに Unrecognized VM option が出るなら、環境変数から入っています。どれも「拾った」と名乗る行を出します。

出どころ出力される行影響範囲(Temurin HotSpot で実測)
JAVA_TOOL_OPTIONSPicked up JAVA_TOOL_OPTIONS: ...javajavac落ちる(=Maven や Gradle がビルドの手前で死ぬ)
_JAVA_OPTIONSPicked up _JAVA_OPTIONS: ...javajavac落ちる
JDK_JAVA_OPTIONSNOTE: Picked up JDK_JAVA_OPTIONS: ...java 起動子のみ。javac は通る

JAVA_TOOL_OPTIONS に古いフラグが入っていると、アプリが起動する前にビルドツール自身が同じエラーで落ちます。「mvn testUnrecognized VM option で落ちるが、java -version も落ちる」という状態なら、まずこの 3 つを疑ってください。他の注入経路(@argfile など)は Security Manager の記事 の「フラグはどこに隠れているか」に一覧があります。

切り分け(うまくいかないとき)

  • フラグを消したのに、別の名前で同じエラーが出る:想定どおりです。JVM は最初の 1 つで止まります。JAVA_OPTS を開いて CMSParNewPermParallelOld を含む行をまとめて消してください。
  • -XX:-UseConcMarkSweepGC(マイナス指定)にしても落ちる:JVM はフラグ名そのものを知りません。+- の別は無関係です。
  • Ignoring option ...; support was removed in N と出るが起動はする:削除ではなく廃止の段です(終了コード 0)。効いてはいないので、次の JDK で削除に進む前に消してください。
  • Unrecognized VM option ではなく Unsupported class file major version が出る:別の機構です。そちらはフラグではなくクラスファイルの版で、ビルドツール側の問題です(Unsupported class file major version の直し方)。
  • アプリではなく mvngradle が落ちるJAVA_TOOL_OPTIONS_JAVA_OPTIONS にフラグが入っています。上の表のとおり、これらは javac を含むすべての JVM に効きます。
  • GC の設定を移したが、停止時間が伸びた:CMS の調整値は G1 に移植できません。停止時間の目標は -XX:MaxGCPauseMillis で表現し、それでも足りなければ ZGC(-XX:+UseZGC)を検討する順になります。GC を明示していないなら、まず何が選ばれているかを確かめてくださいjava -XX:+PrintFlagsFinal -version | grep -E "Use.*GC ")。小さいコンテナでは Serial GC になっていることがあります。

検証環境

  • eclipse-temurin@sha256:89dc1a6e...(JDK 17.0.19)、ネットワーク不要
  • 再現:java -XX:+UseConcMarkSweepGC -cp out AppUnrecognized VM option 'UseConcMarkSweepGC' で終了コード 1(main() に到達せず、APP_STARTED が出ない)
  • 修正:java -XX:+UseG1GC -cp out App が終了コード 0(Java のソースは再現・修正で同一)

再現から修正までは errfix の検証ハーネスが機械的に確認しています。本文の 2 つの表の 11 フラグがすべて JDK 17 で Unrecognized VM option になること(UseParallelOldGC を含む。マイナス指定 -XX:-UseConcMarkSweepGC も同じく拒否されること)、-XX:+ExplicitGCInvokesConcurrent は JDK 17 でも受理されて起動することMaxPermSize が JDK 8 では ignoring option ...; support was removed in 8.0 として無視されたうえで起動していたこと、UseConcMarkSweepGC が JDK 11 で非推奨警告つきに起動すること、JDK 17 が GC を実行環境から選ぶこと(16 CPU / 16 GB と --cpus=2 --memory=2g では G1、--cpus=1 --memory=512m では Serial GC・いずれも {ergonomic})、-XX:+UseZGC-XX:+UseG1GC -XX:MaxGCPauseMillis=100 が JDK 17 で起動すること、-XX:+PrintGCDetails-Xlog:gc* に読み替えられて動くこと、-XX:+IgnoreUnrecognizedVMOptions を足すと起動すること、JAVA_TOOL_OPTIONS_JAVA_OPTIONS では javac 自身が落ち JDK_JAVA_OPTIONS では落ちないことは、いずれも Eclipse Temurin の JDK 8・11・17・21 で実測しています。

CMS collector が JDK 14 で削除された(JEP 363)こと、server-class の判定条件(2 プロセッサ以上かつ 1792 MB 以上)、UseParallelOldGC が JDK 16 で削除されたことは、OpenJDK / Oracle の一次情報に拠ります。 フラグが JDK 14 で廃止(Ignoring option)され JDK 15 で削除(Unrecognized VM option)に進んだ点は、Temurin に 12〜15 のイメージが無いため AdoptOpenJDK のイメージで測定しました(記録は docs/probes/jvm-legacy-flags-20260713.md)。GC の性能特性は実測していません。

検証(machine-verified)

この修正は eclipse-temurin@sha256:89dc1a6e09920ea26b2ede6fddfcac1a7508b50159a6d04c918a46132953aab6 のバージョン固定コンテナ内で再現し、修正後に Unrecognized VM option 'UseConcMarkSweepGC' のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs jvm/cms-vm-option-removed
● reproduce Unrecognized VM option 'UseConcMarkSweepGC' present ✓
● apply fix exit 0
● re-run Unrecognized VM option 'UseConcMarkSweepGC' gone ✓
PASS verified · eclipse-temurin@sha256:89dc1a6e09920ea26b2ede6fddfcac1a7508b50159a6d04c918a46132953aab6 · signature gone

確認したのは上のイメージの中だけです。別の環境で直らなかった、記述が違う、という場合は 報告してください(対象と検証イメージは件名・本文に入ります)。