1. 序章

このチュートリアルでは、 java.lang.VerifyErrorエラーの原因とそれを回避するための複数の方法を見ていきます。

2. 原因

Java仮想マシン(JVM)は、ロードされたすべてのバイトコードをJavaセキュリティモデルのコア原則として信頼していません。 実行時に、JVMは .class ファイルをロードし、それらをリンクして実行可能ファイルを形成しようとしますが、これらのロードされた.classファイルの有効性は不明です。

ロードされた.classファイルが最終的な実行可能ファイルに脅威を与えないようにするために、JVMは.classファイルに対して検証を実行します。 さらに、JVMはバイナリが整形式であることを保証します。 たとえば、JVMは、クラスがfinalクラスをサブタイプ化していないことを確認します。

多くの場合、新しいバージョンのJavaは、古いバージョンよりも厳密な検証プロセスを使用しているため、有効で悪意のないバイトコードで検証が失敗します。 たとえば、JDK 13は、JDK7では実施されなかった検証ステップを追加した可能性があります。 したがって、JVM 13でアプリケーションを実行し、古いバージョンの Javaコンパイラ(javac)でコンパイルされた依存関係を含めると、JVMは古い依存関係を無効と見なす可能性があります。

したがって、古い .class ファイルを新しいJVMにリンクすると、JVMは次のようなjava.lang.VerifyErrorをスローする可能性があります。

java.lang.VerifyError: Expecting a stackmap frame at branch target X
Exception Details:
  Location:
    
com/example/baeldung.Foo(Lcom/example/baeldung/Bar:Baz;)Lcom/example/baeldung/Foo; @1: infonull
  Reason:
    Expected stackmap frame at this location.
  Bytecode:
    0000000: 0001 0002 0003 0004 0005 0006 0007 0008
    0000010: 0001 0002 0003 0004 0005 0006 0007 0008
    ...

この問題を解決するには、次の2つの方法があります。

  • 更新されたjavacでコンパイルされたバージョンへの依存関係を更新します
  • Java検証を無効にする

3. プロダクションソリューション

検証エラーの最も一般的な原因は、古いバージョンのjavacでコンパイルされた新しいバージョンのJVMを使用してバイナリをリンクすることです。 これは、依存関係にJavassist などのツールによって生成されたバイトコードがある場合によく見られます。ツールが古くなっていると、古くなったバイトコードが生成された可能性があります。

この問題を解決するには、依存関係を、アプリケーションのビルドに使用されたJDKバージョンと一致するJDKバージョンを使用してビルドされたバージョンに更新します。 たとえば、JDK 13を使用してアプリケーションをビルドする場合、依存関係はJDK13を使用してビルドする必要があります。

互換性のあるバージョンを見つけるには、依存関係のJARマニフェストファイルBuild-Jdk を調べて、アプリケーションのビルドに使用されたJDKバージョンと一致することを確認します。

4. デバッグおよび開発ソリューション

アプリケーションをデバッグまたは開発する場合、クイックフィックスとして検証を無効にすることができます。

このソリューションを本番コードに使用しないでください

検証を無効にすることで、JVMは悪意のあるコードや欠陥のあるコードをアプリケーションにリンクし、実行時にセキュリティの侵害やクラッシュを引き起こす可能性があります。

また、JDK 13の時点で、このソリューションは非推奨になっています。このソリューションが将来のJavaリリースで機能することは期待できません。 検証を無効にすると、次の警告が表示されます。

Java HotSpot(TM) 64-Bit Server VM warning: Options -Xverify:none and -noverify were deprecated
  in JDK 13 and will likely be removed in a future release.

バイトコード検証を無効にするメカニズムは、コードの実行方法によって異なります。

4.1. コマンドライン

コマンドラインで検証を無効にするには、noverifyフラグをjavaコマンドに渡します。

java -noverify Foo.class

-noverifyは-Xverify:none のショートカットであり、両方を同じように使用できることに注意してください

4.2. Maven

Maven ビルドで検証を無効にするには、noverifyフラグを任意のプラグインに渡します。

<plugin>
    <groupId>com.example.baeldung</groupId>
    <artifactId>example-plugin</artifactId>
    <!-- ... -->
    <configuration>
        <argLine>-noverify</argLine>
        <!-- ... -->
    </configuration>
</plugin>

4.3. Gradle

Gradle ビルドで検証を無効にするには、noverifyフラグを任意のタスクに渡します。

someTask {
    // ...
    jvmArgs = jvmArgs << "-noverify"
}

5. 結論

このクイックチュートリアルでは、JVMがバイトコード検証を実行する理由と、java.lang.VerifyErrorエラーの原因を学習しました。 また、本番ソリューションと非本番ソリューションの2つのソリューションについても検討しました。

可能であれば、検証を無効にするのではなく、最新バージョンの依存関係を使用してください。