Causes and Avoidance of java.lang.VerifyError – java.lang.VerifyError的原因和避免方法

最后修改: 2019年 11月 1日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

In this tutorial, we’ll look at the cause of java.lang.VerifyError errors and multiple ways to avoid it.

在本教程中,我们将研究java.lang.VerifyError错误的原因和避免它的多种方法。

2. Cause

2.原因

The Java Virtual Machine (JVM) distrusts all loaded bytecode as a core tenet of the Java Security Model. During runtime, the JVM will load .class files and attempt to link them together to form an executable — but the validity of these loaded .class files is unknown.

Java 虚拟机(JVM)不信任所有加载的字节码,这是 Java 安全模型的一个核心原则。在运行期间,JVM将加载.class文件,并试图将它们连接起来以形成一个可执行文件–但这些加载的.class文件的有效性是未知的。

To ensure that the loaded .class files do not pose a threat to the final executable, the JVM performs verification on the .class files. Additionally, the JVM ensures that binaries are well-formed. For example, the JVM will verify classes do not subtype final classes.

为了确保加载的.class文件不会对最终的可执行文件构成威胁,JVM会对.class文件进行验证。此外,JVM还确保二进制文件的格式良好。例如,JVM将验证类不对final类进行子类型。

In many cases, verification fails on valid, non-malicious bytecode because a newer version of Java has a stricter verification process than older versions. For example, JDK 13 may have added a verification step that was not enforced in JDK 7. Thus, if we run an application with JVM 13 and include dependencies compiled with an older version of the Java Compiler (javac), the JVM may consider the outdated dependencies to be invalid.

在许多情况下,对有效的、非恶意的字节码的验证会失败,因为较新版本的Java比旧版本有更严格的验证过程。例如,JDK 13可能增加了一个在JDK 7中没有执行的验证步骤。因此,如果我们用JVM 13运行一个应用程序,并包括用旧版本的Java编译器(javac)编译的依赖关系,JVM可能认为过时的依赖关系是无效的。

Thus, when linking older .class files with a newer JVM, the JVM may throw a java.lang.VerifyError similar to the following:

因此,当用较新的JVM链接旧的.class文件时,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
    ...

There are two ways to solve this problem:

有两种方法来解决这个问题。

  • Update dependencies to versions compiled with an updated javac
  • Disable Java verification

3. Production Solution

3.生产解决方案

The most common cause of a verification error is linking binaries using a newer JVM version compiled with an older version of javac. This is more common when dependencies have bytecode generated by tools such as Javassist, which may have generated outdated bytecode if the tool is outdated.

导致验证错误的最常见原因是使用较新的JVM版本与较旧版本的javac编译的二进制文件进行链接。这在依赖关系的字节码由Javassist等工具生成时更为常见,如果该工具已经过时,可能会生成过时的字节码。

To resolve this issue, update dependencies to a version built using a JDK version that matches the JDK version used to build the application. For example, if we build an application using JDK 13, the dependencies should be built using JDK 13.

要解决这个问题,将依赖关系更新为使用与构建应用程序的 JDK 版本相匹配的 JDK 版本构建的版本。例如,如果我们使用 JDK 13 构建应用程序,则依赖项应使用 JDK 13 构建。

To find a compatible version, inspect the Build-Jdk in the JAR Manifest file of the dependency to ensure it matches the JDK version used to build the application.

要找到兼容的版本,请检查依赖关系的JAR Manifest文件中的Build-Jdk,以确保它与用于构建应用程序的JDK版本相符。

4. Debugging & Development Solution

4.调试和开发解决方案

When debugging or developing an application, we can disable verification as a quick-fix.

在调试或开发一个应用程序时,我们可以禁用验证,作为一种快速修复方法。

Do not use this solution for production code.

请勿将此方案用于生产代码

By disabling verification, the JVM can link malicious or faulty code to our applications, resulting in security compromises or crashes when executed.

通过禁用验证,JVM可以将恶意或有问题的代码链接到我们的应用程序,导致安全漏洞或执行时崩溃。

Also note that as of JDK 13, this solution has been deprecated, and we should not expect this solution to work in future Java releases. Disabling verification will result in the following warning:

还要注意的是,从 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.

The mechanism for disabling bytecode verification varies based on how we run our code.

根据我们运行代码的方式,禁用字节码验证的机制是不同的。

4.1. Command Line

4.1.命令行

To disable verification on the command line, pass the noverify flag to the java command:

要在命令行上禁用验证,请将noverify标志传递给java命令。

java -noverify Foo.class

Note that -noverify is a shortcut for-Xverify:none and both can be used interchangeably.

请注意,-noverify-Xverify:none的快捷方式,两者可以互换使用

4.2. Maven

4.2.Maven

To disable verification in a Maven build, pass the noverify flag to any desired plugin:

要在Maven构建中禁用验证,请将noverify标志传递给任何需要的插件。

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

4.3. Gradle

4.3.Gradle

To disable verification in a Gradle build, pass the noverify flag to any desired task:

要在Gradle构建中禁用验证,请将noverify标志传递给任何需要的任务。

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

5. Conclusion

5.总结

In this quick tutorial, we learned why the JVM performs bytecode verification and what causes the java.lang.VerifyError error. We also explored two solutions: A production one and a non-production one.

在这个快速教程中,我们了解了为什么JVM要进行字节码验证,以及什么原因导致了java.lang.VerifyError错误。我们还探索了两种解决方案。一个是生产型的,一个是非生产型的。

When possible, use the latest versions of dependencies rather than disabling verification.

在可能的情况下,使用依赖的最新版本,而不是禁用验证。