OpenJDK 64-bit VM「sharing is only supported…」が出た時の対処法

OpenJDK 64-bit VM「sharing is only supported…」エラー徹底解説と完全対処法

導入:謎のエラーメッセージの正体を暴く

Javaアプリケーションを実行しようとした際、あるいは特定のツールを起動した際に、以下のようなエラーメッセージに遭遇した経験はないでしょうか?

OpenJDK 64-Bit Server VM warning: sharing is only supported for boot loader classes because bootstrap classes have been transformed by an agent

この警告メッセージは、プログラムの実行を妨げる致命的なエラーではない場合もあれば、後続の動作に影響を与える場合もあります。特に、Javaの起動時間を短縮したり、メモリ使用量を削減したりするための機能である「共有クラス機能(Class Data Sharing – CDS)」を利用しようとしている環境で、このメッセージが出やすい傾向にあります。

しかし、「sharing is only supported…」「boot loader classes」「bootstrap classes」「transformed by an agent」といった専門用語が並んでおり、一体何が問題なのか、どのように対処すれば良いのか、初見では戸惑う方も多いでしょう。

この記事では、このOpenJDKが出力する警告メッセージが示す内容を深く掘り下げ、なぜこの問題が発生するのか、そして具体的にどのような対処法があるのかを、詳細かつ網羅的に解説します。約5000語にわたるこの解説を通じて、読者の皆様がこのエラーメッセージに遭遇した際に、自信を持って原因を特定し、適切な対処を行えるようになることを目指します。

エラーメッセージの詳細な分析:キーワードが示す意味

まずは、エラーメッセージを分解し、それぞれのキーワードが何を示しているのかを理解することから始めましょう。

OpenJDK 64-Bit Server VM warning: sharing is only supported for boot loader classes because bootstrap classes have been transformed by an agent

  1. OpenJDK 64-Bit Server VM warning:: これは、OpenJDKの64ビット版サーバーVMからの警告であることを示しています。致命的なエラーではなく、「警告」であるため、多くの場合プログラム自体は起動しますが、想定される最適化(ここでは共有クラス機能)が有効にならない可能性を示唆しています。
  2. sharing is only supported for boot loader classes: ここでの「sharing」は、Javaの「共有クラス機能(Class Data Sharing – CDS)」を指しています。これは、複数のJava仮想マシン(JVM)プロセス間でクラスデータを共有することで、起動時間の短縮やメモリ使用量の削減を図る技術です。このフレーズは、「(共有クラス機能による)共有は、ブートローダーがロードするクラスに対してのみサポートされます」という意味です。
  3. because bootstrap classes have been transformed by an agent: これが問題の根源を示しています。「なぜなら、ブートストラップクラスがエージェントによって変換されたから」という意味です。「bootstrap classes」は、JVMが起動する際に最初にロードされる、最も基本的なJavaクラス群です(例: java.lang.Object, java.lang.String, コレクションフレームワークの基本クラスなど)。これらはJava Runtime Environment (JRE) の rt.jar やモジュールパス内のコアモジュールに含まれています。「transformed by an agent」は、Javaエージェント(Java Agent)と呼ばれる特殊なプログラムによって、これらのブートストラップクラスのバイトコードが実行時に変更(変換)されたことを意味します。

つまり、この警告メッセージ全体は、「JVMが共有クラス機能を使おうとしたが、JVMの起動に不可欠なブートストラップクラスが、Javaエージェントによって変更されていることが検出された。共有クラス機能は、変更されていない(ブートローダーがロードする)クラスでのみ正しく機能するため、今回は共有クラス機能が有効になりません(あるいは部分的にしか有効になりません)」という状況を伝えています。

技術的な背景:共有クラス機能(CDS)とJavaエージェント

このエラーを理解するためには、背後にある二つの重要なJava技術、共有クラス機能(CDS)とJavaエージェントについて、もう少し詳しく知る必要があります。

共有クラス機能(Class Data Sharing – CDS)とは?

共有クラス機能(CDS)は、Java SE 5で導入された技術で、Java 8以降、特にJava 11以降で大きく進化し、Default CDSやApplication CDS (AppCDS) として広く利用されるようになりました。

CDSの基本的なアイデアは以下の通りです。

  1. クラスデータのアーカイブ: JVMが起動する際にロードされるクラスのメタデータ(クラスの構造、メソッド情報、フィールド情報など)は、通常、各JVMプロセスが独自のメモリ領域に保持します。CDSでは、これらの頻繁に使用されるクラスのメタデータを、起動前に一度、「共有アーカイブファイル」としてディスクに保存します。
  2. アーカイブの共有: 複数のJVMプロセスが起動する際に、この共有アーカイブファイルをメモリマップドファイルとして読み込み、同じクラスデータを共有します。

これにより、以下のようなメリットが得られます。

  • 起動時間の短縮: クラスの読み込みと検証のフェーズをスキップし、アーカイブから高速にデータをロードできるため、JVMの起動時間が短縮されます。特に、多くのクラスをロードする大規模なアプリケーションや、マイクロサービスのように頻繁に起動・停止するプロセスで効果を発揮します。
  • メモリ使用量の削減: 同じクラスデータを複数のJVMプロセスが共有するため、システム全体のメモリ使用量を削減できます。

CDSには主に以下の二種類があります。

  • Default CDS: JRE/JDKに含まれる標準ライブラリ(ブートストラップクラスやプラットフォームクラス)のクラスデータが事前にアーカイブされているもの。Java 11以降では、多くの場合デフォルトで有効になっています (-Xshare:auto がデフォルトの場合が多い)。
  • Application CDS (AppCDS): アプリケーション独自のクラスパスにあるクラスや、拡張機能クラスパスにあるクラスを含めて、ユーザーが独自のアーカイブを作成できる機能。アプリケーションの起動時間短縮にさらに効果的です。

CDSは、JVMの起動オプション -Xshare で制御されます。
* -Xshare:on: 共有クラス機能を強制的に有効にします。
* -Xshare:off: 共有クラス機能を無効にします。
* -Xshare:auto: 環境に応じて共有クラス機能を自動的に有効にするかを判断します(多くの新しいJDKのデフォルト)。

このエラーメッセージは、CDSが有効になっている、または有効になろうとしている状況で発生しています。

Javaエージェント(Java Agent)とは?

Javaエージェントは、Java SE 5で導入された java.lang.instrument パッケージを通じて提供される機能です。これは、実行中のJavaアプリケーションの動作をフックし、クラスのロード時や実行時に、そのバイトコードを動的に変更(Instrumentation)することを可能にします。

Javaエージェントは、主に以下の目的で使用されます。

  • 監視・プロファイリング: パフォーマンスメトリクス収集、実行時間の計測、スレッドダンプなど。New Relic, Dynatrace, AppDynamicsといったAPM (Application Performance Monitoring) ツールや、VisualVMなどのプロファイリングツールがこれを利用します。
  • デバッグ: 実行中のコードの状態を詳細に調査する。
  • テストカバレッジ計測: 実行されたコードの割合を計測する。JaCoCoなどがこれを利用します。
  • AOP (Aspect-Oriented Programming): 実行中のコードに横断的な関心事(ロギング、トランザクション処理、セキュリティチェックなど)を織り込む。AspectJなどがコンパイル時だけでなく、ロード時にもInstrumentationを利用することがあります。
  • モックオブジェクトの生成: テストのために、実行時にクラスを書き換えてモックオブジェクトを作成する。Mockitoなどが利用する技術の一部に関連します(ただし、直接的なエージェントではない場合もあります)。

Javaエージェントは、通常、JVM起動時に -javaagent:<agent JAR path>[=options] というオプションを指定することで有効化されます。指定されたJARファイル内の特別なマニフェスト属性 (Premain-Class) によって指定されたクラスが、JVM起動のごく初期段階でロードされ、Instrumentation オブジェクトへの参照を受け取ります。このオブジェクトを通じて、エージェントは ClassFileTransformer を登録できます。登録された ClassFileTransformer は、その後JVMがクラスをロードする際に呼び出され、ロード対象のクラスのバイトコードを受け取り、必要に応じて変換したバイトコードをJVMに返します。

Javaエージェントは、JVMのコア機能に深く介入するため、強力な機能であると同時に、その利用には注意が必要です。特に、JVMの起動プロセスに影響を与える可能性があります。

CDSとJavaエージェントの衝突:なぜエラーが発生するのか?

さて、CDSとJavaエージェント、それぞれが何をするものか理解したところで、なぜこれらが同時に使われると問題が発生し、「sharing is only supported…」というエラーが出るのかを見ていきましょう。

CDSは、JVMが起動する前に、特定のクラスの最終的なバイトコードとメタデータをアーカイブとして保存します。このアーカイブは、JVMが起動する際に高速に読み込まれ、クラスロードの効率化を図ります。特に、ブートストラップクラスのようなJVMのコア部分を構成するクラスは、通常は変更されないことを前提にアーカイブに含まれます。

一方、Javaエージェントは、JVM起動のごく初期段階で自身をフックし、その後JVMがクラスをロードする際に、そのクラスのバイトコードを動的に変換(書き換え)する可能性があります。ClassFileTransformer が登録されている場合、JVMがクラスをメモリにロードする直前に、エージェントにバイトコードを渡して変換を要求します。

問題は、Javaエージェントがブートストラップクラスを含む、JVMの基本的なクラス群のバイトコードを変換しようとした場合に発生します。

  1. CDSアーカイブの作成: CDSアーカイブは、Javaエージェントが存在しないか、存在してもブートストラップクラスを変換しないという前提で作成されることが多いです(特にDefault CDSの場合)。アーカイブには、オリジナルの、変換されていないブートストラップクラスのデータが保存されています。
  2. JVM起動時のCDS利用: JVMが起動し、CDSアーカイブからクラスデータを読み込もうとします。これにより、クラスロード処理の一部がスキップされ、高速化が図られます。
  3. JVM起動時のJavaエージェント実行: 同時に、-javaagent オプションが指定されている場合、Javaエージェントが起動し、ClassFileTransformer を登録します。
  4. ブートストラップクラスのロードと変換: JVMがブートストラップクラスをロードしようとした際(厳密には、ロードされた後、定義される前)、登録されたエージェントの ClassFileTransformer が呼び出され、そのブートストラップクラスのバイトコードを変換します。
  5. 整合性の問題: ここで衝突が発生します。CDSは、アーカイブに保存されている変換されていないブートストラップクラスのデータを利用しようとしますが、実際にはエージェントによってそのブートストラップクラスは変換されてしまい、バイトコードやメタデータがアーカイブの内容と異なっています。
  6. JVMの検出と警告: JVMは、CDSが想定するクラスデータと、エージェントによって変換された実際のクラスデータとの間に不整合があることを検出します。特に、ブートストラップクラスのようなコアなクラスが変換されている場合、CDSによる共有が正しく機能しない、あるいはJVMの安定性を損なう可能性があると判断します。
  7. CDSの部分的無効化と警告出力: その結果、JVMはブートストラップクラスを含む一部のクラスに対するCDSを無効化し、「sharing is only supported for boot loader classes because bootstrap classes have been transformed by an agent」という警告を出力します。これは、「(ブートストラップクラスはエージェントに変換されたので共有できませんが)他の(通常はアプリケーションの)クラスについては、ブートローダーがロードするものについて共有を試みます」という意味合いを含んでいる場合もありますが、多くの場合、この警告が出た時点ではCDSによる起動高速化のメリットは大きく失われています。

この問題は、Javaエージェントが ClassFileTransformer を登録し、特にJVMのコアライブラリに含まれるクラス(ブートストラップクラスやプラットフォームクラス)のバイトコードを変換しようとした場合に顕著に発生します。全てのエージェントがブートストラップクラスを変換するわけではありませんが、一部のプロファイリングツールや高度なAOPフレームワークなどは、広範なクラス、あるいはJVM内部のクラスに近いクラスを対象とする場合があります。

具体的な原因特定のシナリオ

このエラーメッセージが出た場合、原因はほぼ確実に「Javaエージェントが有効になっており、かつCDSが有効(または有効化を試みている)」という組み合わせです。しかし、具体的にどのエージェントが悪さをしているのか、どのように有効化されているのかを特定する必要があります。

考えられるシナリオはいくつかあります。

シナリオ1: -javaagent オプションが明示的に指定されている場合

最も一般的なケースです。Javaアプリケーションやツールの起動コマンドラインに、直接 -javaagent:<agent JAR> というオプションが含まれています。

特定方法:

  • 実行しているコマンドラインを確認する。
  • シェルスクリプトやバッチファイル経由で起動している場合は、そのスクリプトの内容を確認する。
  • IDEから実行している場合は、IDEの実行構成(Run Configuration / Debug Configuration)で設定されているJVMオプションを確認する。

例:

  • java -javaagent:/path/to/monitoring-agent.jar -jar myapp.jar
  • Maven/Gradleのプラグイン設定でJVM引数として指定されている。
  • 特定のIDEのデバッグ設定で、エージェントが自動的に付与されている。

シナリオ2: 間接的にJavaエージェントが有効になっている場合

-javaagent オプションを自分で指定した覚えがないのにエラーが出る場合です。

特定方法:

  • 環境変数: JAVA_TOOL_OPTIONS または _JAVA_OPTIONS といった環境変数が設定されていないか確認します。これらの環境変数は、JVMが起動する際に自動的に読み込まれ、そこに指定されたオプションがコマンドラインオプションに追加されます。例えば、JAVA_TOOL_OPTIONS="-javaagent:/some/agent.jar" のように設定されていると、すべてのJavaプロセスでそのエージェントが有効になります。
    • Linux/macOS: echo $JAVA_TOOL_OPTIONS または echo $_JAVA_OPTIONS
    • Windows (Command Prompt): echo %JAVA_TOOL_OPTIONS% または echo %_JAVA_OPTIONS%
    • Windows (PowerShell): $env:JAVA_TOOL_OPTIONS または $env:_JAVA_OPTIONS
  • アプリケーションサーバー/フレームワーク: 特定のアプリケーションサーバーやフレームワークが、内部的にエージェントを使用している、あるいはエージェントを自動的に有効化する設定を持っている場合があります。
    • 例: Spring Boot DevTools はクラスローダーを操作しますが、直接エージェントとして指定されているわけではなくても、CDSと競合する可能性があります(ただし、今回のエラーメッセージはエージェント起因である可能性が高いです)。他のAPMツールなどが、サーバー起動時に自動的にフックするような設定になっていることもあります。
  • 特定のツール: 使用しているIDEやデバッグツールが、プロファイリングなどの目的で内部的にエージェントをアタッチしていることがあります。

シナリオ3: CDS設定が影響している場合

エラーメッセージはエージェントに起因していますが、問題は「エージェントが変換した」ことと「CDSを有効にしようとした」ことの組み合わせです。

特定方法:

  • JVM起動オプションに -Xshare:on-Xshare:auto が含まれているか確認します。
  • Java 11以降の多くの環境では -Xshare:auto がデフォルトであり、特に指定しなくてもCDSが有効になろうとします。この場合、明示的なCDSオプションがなくてもエラーは発生し得ます。
  • AppCDSを使用している場合は、アーカイブ作成時の環境と実行時の環境が一致しているか確認します。エージェントを有効にした状態でAppCDSアーカイブを作成し、それをエージェントなしで実行しようとした場合などにも問題が発生する可能性があります。

対処法:エラーを解消するための具体的なステップ

原因がCDSとJavaエージェントの衝突であると特定できたところで、具体的な対処法をいくつか紹介します。最も簡単で効果的な方法から順に説明します。

対処法1: 共有クラス機能(CDS)を無効にする

これが最も直接的で、多くの場合エラーを解消できる簡単な方法です。CDSを無効にすれば、JVMはクラスデータを共有アーカイブから読み込もうとしなくなるため、エージェントによるクラス変換との衝突が発生しなくなります。

手順:

Javaアプリケーション/ツールの起動コマンドに、以下のJVMオプションを追加します。

bash
-Xshare:off

具体的なコマンド例:

  • Standalone JARの場合:
    bash
    java -Xshare:off -jar your-application.jar
  • -javaagent と組み合わせていた場合:
    bash
    java -Xshare:off -javaagent:/path/to/your-agent.jar -jar your-application.jar

    (この場合、エージェントは引き続き動作しますが、CDSとの衝突は避けることができます。)
  • -javaagent が環境変数 JAVA_TOOL_OPTIONS で設定されている場合:
    環境変数に -Xshare:off を追加します(既存の設定を消さないように注意)。

    • Linux/macOS (Bash/Zsh):
      bash
      export JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -Xshare:off"
      # または、完全に上書きする場合 (既存設定がないことを確認):
      # export JAVA_TOOL_OPTIONS="-Xshare:off -javaagent:/path/to/your-agent.jar"
    • Windows (Command Prompt):
      cmd
      set JAVA_TOOL_OPTIONS=%JAVA_TOOL_OPTIONS% -Xshare:off
      rem または、完全に上書きする場合:
      rem set JAVA_TOOL_OPTIONS=-Xshare:off -javaagent:/path/to/your-agent.jar
    • Windows (PowerShell):
      powershell
      $env:JAVA_TOOL_OPTIONS += " -Xshare:off"
      # または、完全に上書きする場合:
      # $env:JAVA_TOOL_OPTIONS = "-Xshare:off -javaagent:/path/to/your-agent.jar"

      環境変数を変更した後は、新しいシェルやコマンドプロンプトを開き直すか、システムを再起動して変更を反映させる必要がある場合があります。

メリット:

  • エラーメッセージが解消される可能性が非常に高い。
  • 設定が簡単で、すぐに試せる。

デメリット:

  • CDSによる起動時間の短縮やメモリ使用量の削減効果が得られなくなります。
  • ただし、現代の高速なストレージや大容量メモリを搭載したシステムでは、このパフォーマンス差が体感できないことも多いです。アプリケーションの性質(頻繁に起動するか、長時間稼働するかなど)によって影響度は異なります。

考慮事項:

  • CDS無効化によるパフォーマンスへの影響は、実際の環境で測定(ベンチマーク)することをお勧めします。起動時間の計測や、アプリケーション実行中のメモリ使用量を監視して、許容範囲内の変化であるかを確認してください。
  • 多くのユースケースでは、この方法で問題なく運用できます。

対処法2: 原因となっているJavaエージェントを特定し、無効化または設定変更する

もしCDSによるパフォーマンス最適化が非常に重要であり、CDSを無効にしたくない場合は、Javaエージェントの方に対処する必要があります。

手順:

  1. エージェントの特定: シナリオの特定方法を参考に、どの -javaagent オプションが使われているか、あるいはどの環境変数や設定がエージェントを有効にしているかを特定します。複数のエージェントが指定されている場合は、一つずつ(または半分ずつ)無効化してみて、エラーメッセージが出なくなるものを探す「二分探索」のような方法が有効です。
  2. エージェントの無効化: 特定したエージェントの -javaagent オプションを削除するか、関連する環境変数の設定を解除します。
    • 例: java -jar your-application.jar (もし -javaagent が唯一の原因だった場合)
  3. エージェントの設定変更(可能な場合): 一部の高度なJavaエージェントは、Instrumentationの対象とするクラスを細かく設定できるオプションを提供している場合があります。例えば、「特定のパッケージのクラスだけを対象とする」「ブートストラップクラスは対象外とする」といった設定です。使用しているエージェントの公式ドキュメントを参照し、CDSとの互換性に関する情報や、クラス変換の対象を除外する設定がないか探してください。この方法が成功すれば、エージェントの機能とCDSの両方を(ある程度)両立できる可能性がありますが、エージェントの機能によっては不可能であったり、設定が複雑であったりします。

メリット:

  • CDSによるパフォーマンス最適化を引き続き利用できる可能性があります(エージェントを完全に無効にした場合)。
  • エージェントの設定で回避できた場合は、両方のメリットを享受できる可能性があります。

デメリット:

  • 原因となるエージェントを特定する作業が必要で、少し手間がかかる場合があります。
  • エージェントを無効にした場合、そのエージェントが提供していた機能(監視、プロファイリングなど)が利用できなくなります。
  • エージェントの設定変更で対応できるかは、エージェントの実装に依存し、必ずしも成功するとは限りません。特にブートストラップクラスを変換するような強力なエージェントでは、設定での回避が難しいことが多いです。

考慮事項:

  • 特定したエージェントが、アプリケーションの運用上必須のものであるかを確認してください。必須である場合は、エージェントの開発元にCDSとの互換性について問い合わせるか、対処法1(CDS無効化)を検討する必要があります。
  • -javaagent 以外の方法(例: 特定のフレームワーク機能やIDEのデバッグ機能)でクラス操作が行われている場合は、その機能自体を無効にする必要があります。

対処法3: AppCDSアーカイブの再作成(AppCDSを使用している場合)

アプリケーション独自のクラスを含むAppCDSを使用している場合、エージェントが有効な状態でアーカイブを作成し直すことで、このエラーを回避できる可能性があります。これは、JVMがアーカイブ作成時にエージェントによるクラス変換を考慮に入れることで、矛盾のないアーカイブを生成することを期待する方法です。ただし、この方法はエージェントの性質に強く依存し、特にブートストラップクラスを変換するエージェントでは成功しない可能性が高いです。あくまで限定的なシナリオでの対処法と考えてください。

手順概要:

AppCDSアーカイブを作成する基本的な手順は以下の通りですが、エージェントを含める点が重要です。

  1. クラスリストのダンプ: エージェントを有効にした状態で、アプリケーションを一度実行し、ロードされるクラスのリストをファイルに出力します。
    bash
    java -XX:DumpLoadedClassList=classes.lst -javaagent:/path/to/your-agent.jar -jar your-application.jar
    # アプリケーションが正常終了するまで、あるいは必要なクラスが全てロードされるまで実行します。

    (注: アプリケーションの種類によっては、ヘッドレスモードやテスト実行など、必要なクラスをロードできる方法で実行してください。)
  2. 共有アーカイブの作成: ダンプしたクラスリストとエージェントを含めて、共有アーカイブファイルを作成します。
    bash
    java -Xshare:dump -XX:SharedArchiveFile=app.jsa -XX:SharedClassListFile=classes.lst -javaagent:/path/to/your-agent.jar

    (注: ダンプ時とダンプ時のJDKのバージョン・種類を一致させてください。)
  3. 作成したアーカイブの使用: 作成したアーカイブとエージェントを使用して、アプリケーションを実行します。
    bash
    java -Xshare:on -XX:SharedArchiveFile=app.jsa -javaagent:/path/to/your-agent.jar -jar your-application.jar

メリット:

  • CDSとJavaエージェントの両方を利用できる可能性があります(成功した場合)。

デメリット:

  • 手順が複雑です。
  • 多くのJavaエージェント、特にブートストラップクラスを変換するタイプのエージェントでは、この方法でも衝突を完全に回避できない可能性が高いです。JVMがアーカイブ作成時にエージェントの変換を考慮しても、実行時の動的な変換との整合性を完全に保つのは困難な場合があります。
  • アーカイブの作成に時間がかかります。
  • アーカイブは、作成時と全く同じJDKバージョン、エージェントのバージョン、エージェントの設定でしか信頼性がありません。いずれかが変わると、アーカイブが無効になるか、再度エラーが発生する可能性があります。

考慮事項:

  • この方法は、Javaエージェントがブートストラップクラスに影響を与えず、主にアプリケーションクラスのみを変換する場合に成功する可能性が比較的高いです。しかし、このエラーメッセージが出ているということは、ブートストラップクラスに影響が出ている可能性が高いです。
  • まずは対処法1か2を試すことを強くお勧めします。この方法は、他の方法がどうしても使えず、かつCDSとエージェントの両方が必須であるような、非常に限定的な状況で試す価値があるかもしれません。

対処法4: 環境変数 JAVA_TOOL_OPTIONS_JAVA_OPTIONS の再確認

意図しない -javaagent-Xshare:on 設定がこれらの環境変数に入り込んでいることが、エラーの原因である場合があります。特に、以前に何らかのツールや設定を試した際にこれらの変数を設定し、そのまま忘れているケースです。

手順:

オペレーティングシステムのターミナルやコマンドプロンプトで、前述の「シナリオ2」の特定方法を参考に、これらの環境変数の値を確認します。もし設定されている場合は、不要なオプションを削除するか、必要に応じて適切なオプション(例: -Xshare:off)を追加して再設定します。

対処法5: 使用しているJavaバージョン/ディストリビューションの確認

ごく稀にですが、使用しているOpenJDKの特定のバージョンやディストリビューション(Oracle JDK, Adoptium Temurin, Azul Zulu, Amazon Correttoなど)によって、CDSやエージェントの挙動に微妙な違いがある可能性はゼロではありません。

手順:

java -version コマンドを実行して、使用しているJavaの正確なバージョンとディストリビューションを確認します。もし古いバージョンを使用している場合は、最新の安定バージョンにアップデートしてみることで、問題が解消される可能性があります。また、異なるディストリビューションに切り替えてみることも、原因の切り分けに役立つ場合があります。

対処法6: コンテナ環境での注意点

Dockerなどのコンテナ環境でJavaアプリケーションを実行している場合、問題はコンテナのビルドプロセスや実行設定にあることが多いです。

手順:

  • Dockerfileの確認: Dockerfileの中に ENV JAVA_TOOL_OPTIONS ... といった環境変数の設定や、CMD または ENTRYPOINT で指定されているJava起動コマンドに -javaagent-Xshare オプションが含まれていないか確認します。
  • ベースイメージの確認: 使用しているベースイメージに、デフォルトで特定のエージェントやツールがインストールされており、それが自動的に有効化されるような設定になっていないか確認します。
  • コンテナ起動オプションの確認: docker run コマンドやDocker Composeファイル、Kubernetesのマニフェストなどで、環境変数 (-e オプションや env フィールド) やコマンド (command フィールド) が設定されていないか確認します。

コンテナ環境の場合、環境変数やコマンドラインオプションは、ホストOSではなくコンテナ内部で設定されるため、コンテナの定義ファイルを重点的に確認する必要があります。

パフォーマンスへの影響についての考察

CDSを無効化すること(対処法1)が最も簡単で確実な解決策であることが多いですが、気になるのはそのパフォーマンスへの影響です。

CDSは主にJVMの起動時間を短縮することを目的としています。アプリケーションが多くのクラスをロードする場合、CDSによってクラスロードと検証の時間が大幅に削減され、体感できるほど起動が速くなることがあります。しかし、アプリケーションが一度起動してしまえば、実行中のパフォーマンス(スループットや応答時間)に対するCDSの直接的な影響は小さいとされています。

また、CDSはクラスデータのメモリ使用量を削減する効果もあります。複数のJVMプロセスが同じCDSアーカイブを共有する場合、各プロセスはクラスデータのコピーを持つ必要がなくなり、システム全体として必要なメモリ量が減ります。これも特に、多くのJavaプロセスを同時に実行するサーバー環境などでメリットがあります。

しかし、これらの効果は、現代のハードウェアの進化により、以前ほど劇的ではなくなっている場合もあります。高速なSSDストレージ、大容量のメモリ、そしてJVM自体の最適化(例えば、新しいGCアルゴリズムやJust-In-Timeコンパイラ)により、CDSが無効であっても十分なパフォーマンスが得られるケースも増えています。

したがって、CDS無効化によるパフォーマンスへの影響を評価する際は、以下の点を考慮してください。

  • アプリケーションの種類: 起動・停止を繰り返すバッチ処理やマイクロサービスでは起動時間が重要ですが、長時間稼働するサーバーアプリケーションでは起動時間の優先度は低いかもしれません。
  • 実行環境: 高速なSSDと十分なメモリがある環境では、影響が小さい傾向があります。
  • 具体的な測定: 最も信頼できるのは、CDSを有効にした場合(エージェントを無効にするなどしてエラーが出ない状態)と、CDSを無効にした場合で、実際のアプリケーションの起動時間やメモリ使用量を計測する(ベンチマークを取る)ことです。

もしCDS無効化によるパフォーマンス低下が許容できないレベルであると判明した場合にのみ、より複雑な対処法(エージェントの設定変更やAppCDS再作成など)を検討するのが現実的なアプローチと言えるでしょう。多くの場合、CDSを無効にしても実用上の問題は発生しないはずです。

付録:さらに深く知りたい方へ

JVMの起動プロセスとクラスローディング

JVMの起動は多段階で行われます。その中でCDSとJavaエージェントが関わる部分を簡単に見てみましょう。

  1. 初期化: JVMプロセスが開始され、基本的なC++コードで書かれたVMの初期化が行われます。
  2. ブートストラップクラスローダー: ネイティブコードで実装されたブートストラップクラスローダーが起動し、JVMのコアライブラリ(java.lang.* など)をロードし始めます。これらのクラスは通常、JRE/JDKの内部パス(lib/rt.jar やモジュールパス)にあります。
  3. Javaエージェントのアタッチ: -javaagent オプションが指定されている場合、この早い段階で指定されたエージェントJARがロードされ、その Premain-Class が実行されます。ここでエージェントは java.lang.instrument.Instrumentation オブジェクトを取得し、必要に応じて ClassFileTransformer を登録します。
  4. CDSアーカイブの読み込み: CDSが有効な場合、JVMはこの段階で共有アーカイブファイルをメモリにマップし、アーカイブ内のクラスデータを読み込みます。これにより、後続のクラスロード処理の一部がスキップされます。アーカイブにはブートストラップクラスの情報も含まれています。
  5. クラスのロードと変換: JVMがアプリケーションが必要とするクラスをロードする際、以下の順序で処理されることがあります(簡略化)。
    • クラスパスやモジュールパスからクラスファイルを見つける。
    • CDSが有効な場合、共有アーカイブにそのクラスの情報があるか確認する。もしあれば、そこからメタデータを取得。
    • 共有アーカイブにない場合、クラスファイルを読み込む。
    • ClassFileTransformer が登録されている場合、読み込んだバイトコードがTransformerに渡され、変換される。ブートストラップクラスであっても、エージェントが対象としていればこの処理が行われます。
    • 変換された(または元の)バイトコードがJVM内部で検証され、クラスとして定義される。
  6. 矛盾の検出と警告: ステップ4でCDSが期待するブートストラップクラスのデータと、ステップ5でエージェントによって実際に変換されたブートストラップクラスのデータが異なる場合、JVMは矛盾を検出し、CDSの共有を部分的に無効にし、警告メッセージを出力します。

java.lang.instrument APIの概要

java.lang.instrument パッケージは、Javaプログラムの実行を監視、計測、および介入するためのAPIを提供します。主な機能は以下の通りです。

  • Instrumentation インターフェース: エージェントの premain または agentmain メソッドに渡されるオブジェクト。これにより、クラスローディングをフックしたり、既にロードされたクラスを再変換したりする機能にアクセスできます。
  • ClassFileTransformer インターフェース: このインターフェースを実装したオブジェクトを Instrumentation に登録することで、JVMがクラスファイルをバイトコードに変換する直前に、そのバイトコードを変更する機会を得られます。transform メソッドは、ローダー、クラス名、定義元のバイト配列などを受け取り、変換済みのバイト配列を返します。
  • retransformClasses メソッド: 既にロードされたクラスを再変換するためのメソッド。これを使うエージェントは、JVMの特定の状態に影響を与える可能性があります。

Javaエージェントがブートストラップクラスを変換することは、JVMの非常に低レベルな部分に手を加えることに等しく、CDSが前提とする安定性や整合性を容易に崩してしまいます。このため、ブートストラップクラスの変換はCDSとの互換性において特に問題になりやすいのです。

他のJVMオプションとの関連性

  • GCオプション: ガベージコレクタの選択や設定はメモリ使用量に大きく影響しますが、CDSはクラスデータ領域に影響するものであり、ヒープ領域の管理を行うGCとは直接的な衝突は通常ありません。ただし、システム全体のメモリ制約下では、CDSによるメモリ削減効果がGCの効率に間接的に影響を与える可能性はあります。
  • JITコンパイラオプション: JITコンパイラは、実行中のバイトコードをネイティブコードにコンパイルしてパフォーマンスを向上させます。CDSはクラスのロード段階を最適化するものであり、JITコンパイルとは異なるフェーズの技術です。直接の衝突は通常ありません。

まとめとトラブルシューティングフロー

このエラーメッセージが出た際のトラブルシューティングフローを再整理します。

  1. エラーメッセージの確認: sharing is only supported... というメッセージが出ていることを確認します。
  2. 原因の仮説: 「CDSが有効(または有効になろうとしている)」かつ「Javaエージェントが有効で、ブートストラップクラスを変換している」という組み合わせである可能性が高いと仮説を立てます。
  3. CDSが有効か確認: 起動オプションや環境変数に -Xshare:on があるか、またはデフォルトの -Xshare:auto の設定になっているか確認します。
  4. Javaエージェントが有効か確認:
    • コマンドラインに -javaagent オプションがあるか確認します。
    • 環境変数 JAVA_TOOL_OPTIONS または _JAVA_OPTIONS-javaagent オプションがあるか確認します。
    • 使用しているフレームワーク、アプリケーションサーバー、またはIDEの設定で、エージェントが自動的に有効になっていないか確認します。
  5. 最も簡単な対処法を試す: 起動オプションに -Xshare:off を追加して、エラーメッセージが消えるか確認します。多くのケースでこれで解決します。
  6. パフォーマンス評価: -Xshare:off で問題が解決した場合、アプリケーションの起動時間やメモリ使用量への影響を評価します。許容できる範囲であれば、この設定で運用を続けます。
  7. 他の対処法の検討(パフォーマンスが許容できない場合、またはエージェント機能が必須の場合):
    • 原因となっているエージェントを特定し、その -javaagent オプションを削除するか、関連設定を無効にします。これにより、エージェントの機能は失われますが、CDSが有効になる可能性があります。
    • もしエージェントの機能が必須であれば、エージェントのドキュメントを確認し、CDSとの互換性に関する設定や、クラス変換の対象外設定がないか探します。設定で回避できれば、エージェントとCDSの両立が可能かもしれません。
    • AppCDSを使用しており、かつエージェントの機能が必須である特殊なケースでは、エージェントを有効にした状態でAppCDSアーカイブを作成し直す方法も検討できますが、成功の保証は低く、推奨度は低いです。
  8. 環境変数の再確認: 意図しない環境変数設定が残っていないか、再度念入りに確認します。
  9. Javaバージョン/ディストリビューションの確認: 問題が解決しない場合、Javaのバージョンやディストリビューションを最新にする、あるいは別のディストリビューションを試すことも検討します。
  10. コンテナ環境の確認: コンテナで実行している場合は、Dockerfileや実行設定、ベースイメージを確認します。

この系統的なアプローチを取ることで、問題を効率的に特定し、適切な対処法を見つけることができるはずです。

まとめ

OpenJDK 64-bit VMから出力される「sharing is only supported for boot loader classes because bootstrap classes have been transformed by an agent」という警告メッセージは、Javaの共有クラス機能(CDS)とJavaエージェントによるバイトコード変換が衝突していることを示しています。具体的には、JavaエージェントがJVMの核となるブートストラップクラスを変換したため、CDSが前提とするクラスデータの整合性が崩れている状態です。

この問題を解決するための最も簡単で効果的な方法は、JVM起動オプションに -Xshare:off を追加して共有クラス機能(CDS)を無効にすることです。これにより、CDSによる起動時間短縮やメモリ削減効果は得られなくなりますが、エラーメッセージは解消され、アプリケーションは正常に起動・実行できるようになることがほとんどです。多くの現代的な環境では、CDS無効化によるパフォーマンスへの影響は限定的であり、許容できる範囲内であることが多いです。

もしCDSによる最適化がどうしても必要である場合、次に検討すべきは、原因となっているJavaエージェントを特定し、そのエージェントを無効にするか、あるいはエージェントの設定を変更してブートストラップクラスを変換しないようにすることです。これはエージェントの種類や設定可能性に依存するため、簡単ではない場合もあります。

AppCDSを使用している場合にエージェントを有効にした状態でアーカイブを作成し直す方法も理論上は考えられますが、複雑であり、特にブートストラップクラスを変換するエージェントに対しては成功する可能性が低いため、推奨度は高くありません。

エラーメッセージが出た際は、まず起動コマンド、環境変数、アプリケーション設定を注意深く確認し、どのJavaエージェントが有効になっているか、CDSがどのように設定されているかを把握することが重要です。その後、最も影響の少ない -Xshare:off から試していくのが合理的なトラブルシューティングの進め方と言えるでしょう。

この記事が、OpenJDKの「sharing is only supported…」エラーに直面した際の理解と解決の助けとなれば幸いです。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール