【问题标题】:What causes java.lang.IncompatibleClassChangeError?是什么导致 java.lang.IncompatibleClassChangeError?
【发布时间】:2010-12-31 03:05:13
【问题描述】:

我将 Java 库打包为 JAR,当我尝试从中调用方法时,它会抛出许多 java.lang.IncompatibleClassChangeErrors。这些错误似乎是随机出现的。什么样的问题可能导致此错误?

【问题讨论】:

  • 在我测试 Apache FOP 1.0 和 Barcode4J 的 Eclipse 项目中,Barcode4J 附带的附加库显然覆盖了 FOP 附带的库(有些具有更高的版本号)。在构建路径/类路径中放置的内容要非常小心。

标签: java jar runtime-error binary-compatibility


【解决方案1】:

这意味着您在未重新编译客户端代码的情况下对库进行了一些不兼容的二进制更改。 Java Language Specification §13 详细介绍了所有此类更改,最突出的是,将非static 非私有字段/方法更改为static,反之亦然。

针对新库重新编译客户端代码,您应该一切顺利。

更新:如果您发布公共库,则应尽可能避免进行不兼容的二进制更改,以保留所谓的“二进制向后兼容性”。理想情况下,单独更新依赖项 jar 不应破坏应用程序或构建。如果您确实必须破坏二进制向后兼容性,请在发布更改之前增加主要版本号(例如,从 1.x.y 到 2.0.0)recommended

【讨论】:

  • 由于某种原因,这里的开发人员遇到了重新编译客户端代码并不能完全解决问题的问题。出于某种原因,如果他们编辑发生错误的文件并重新编译该错误,则该错误不再出现,但在项目中引用该库的其他地方会随机弹出更多错误。我很好奇这可能是什么原因。
  • 您是否尝试过干净构建(删除所有*.class 文件)并重新编译?编辑文件也有类似的效果。
  • 没有动态生成的代码....除非您认为 JSP 是这样的。我们确实尝试删除类文件,但这似乎没有帮助。奇怪的是,它似乎并没有发生在我身上,但它发生在其他开发者身上。
  • 您能否确保在进行干净构建时,您使用的是相同的 jar 进行编译?
  • 32位或64位平台有什么问题吗,我发现一个错误只在64位平台上执行。
【解决方案2】:

您新打包的库与旧版本不向后二进制兼容 (BC)。由于这个原因,一些未重新编译的库客户端可能会抛出异常。

这是 Java 库 API 中的完整更改列表,这些更改可能会导致使用旧版本库构建的客户端在运行时抛出 java.lang。IncompatibleClassChangeError在一个新的(即打破 BC)上:

  1. 非最终字段变为静态,
  2. 非常量字段变为非静态,
  3. 类变成接口,
  4. 接口变成类,
  5. 如果向类/接口添加新字段(或添加新的超类/超接口),则来自客户端类 C 的超接口的静态字段可能会隐藏添加的字段(同名)继承自 C 的超类(非常罕见的情况)。

注意:还有很多other exceptions是由其他不兼容的变化引起的:NoSuchFieldErrorNoSuchMethodErrorIllegalAccessErrorInstantiationErrorVerifyErrorNoClassDefFoundErrorAbstractMethodError

关于 BC 的更好的论文是 Jim des Rivières 撰写的 "Evolving Java-based APIs 2: Achieving API Binary Compatibility"

还有一些自动工具可以检测此类变化:

在您的库中使用 japi-compliance-checker:

japi-compliance-checker OLD.jar NEW.jar

clirr工具的使用:

java -jar clirr-core-0.6-uber.jar -o OLD.jar -n NEW.jar

祝你好运!

【讨论】:

    【解决方案3】:

    虽然这些答案都是正确的,但解决问题往往更加困难。它通常是类路径上相同依赖项的两个略微不同版本的结果,并且几乎总是由与最初编译时针对类路径上的不同超类某些传递闭包的导入引起的不同,但通常在类实例化和构造函数调用中。 (成功加载类并调用 ctor 后,你会得到 NoSuchMethodException 之类的。)

    如果该行为出现随机,则可能是多线程程序类加载不同传递依赖项的结果,该类基于首先命中的代码。

    要解决这些问题,请尝试使用 -verbose 作为参数启动 VM,然后查看发生异常时正在加载的类。你应该看到一些令人惊讶的信息。例如,拥有相同依赖项和版本的多个副本,如果您知道它们被包含在内,或者您会接受它们。

    使用 Maven 解决重复的 jar 最好结合 Maven 下的 maven-dependency-pluginmaven-enforcer-plugin(或 SBT 的 Dependency Graph Plugin,然后将这些 jar 添加到顶级 POM 的一部分或作为导入的依赖元素在 SBT 中(删除那些依赖项)。

    祝你好运!

    【讨论】:

    • 详细的论点帮助我确定了哪些 jars 存在问题。谢谢
    【解决方案4】:

    我还发现,当使用JNI,从C++调用Java方法时,如果你以错误的顺序将参数传递给被调用的Java方法,当你尝试使用被调用方法内部的参数时,你会得到这个错误(因为它们不会是正确的类型)。我最初感到惊讶的是,当您调用该方法时,JNI 不会在类签名检查中为您执行此检查,但我认为他们不会进行此类检查,因为您可能正在传递多态参数并且他们必须假设你知道自己在做什么。

    C++ JNI 代码示例:

    void invokeFooDoSomething() {
        jobject javaFred = FredFactory::getFred(); // Get a Fred jobject
        jobject javaFoo = FooFactory::getFoo(); // Get a Foo jobject
        jobject javaBar = FooFactory::getBar(); // Get a Bar jobject
        jmethodID methodID = getDoSomethingMethodId() // Get the JNI Method ID
    
    
        jniEnv->CallVoidMethod(javaFoo,
                               methodID,
                               javaFred, // Woops!  I switched the Fred and Bar parameters!
                               javaBar);
    
        // << Insert error handling code here to discover the JNI Exception >>
        //  ... This is where the IncompatibleClassChangeError will show up.
    }
    

    示例 Java 代码:

    class Bar { ... }
    
    class Fred {
        public int size() { ... }
    } 
    
    class Foo {
        public void doSomething(Fred aFred, Bar anotherObject) {
            if (name.size() > 0) { // Will throw a cryptic java.lang.IncompatibleClassChangeError
                // Do some stuff...
            }
        }
    }
    

    【讨论】:

    • 感谢您的提示。在使用提供的错误实例 (this) 调用 Java 方法时,我遇到了同样的问题。
    【解决方案5】:

    我遇到了同样的问题,后来我发现我在 Java 1.4 版上运行应用程序,而应用程序在 6 版上编译。

    实际上,原因是有一个重复的库,一个位于类路径中,另一个包含在位于类路径中的 jar 文件中。

    【讨论】:

    • 你是怎么查到这件事的?我确定我有类似的问题,但不知道如何跟踪正在复制的依赖库。
    • @beterthanlife 编写一个脚本,在所有 jar 文件中搜索重复的类(通过它们的全名搜索,即包名):)
    • 好的,使用 NetBeans 和 maven-shade 我想我可以看到所有被复制的类以及它们所在的 jar 文件。其中许多 jar 文件我没有在我的 pom 中直接引用.xml,所以我认为它们必须包含在我的依赖项中。有没有一种简单的方法可以找出包含它们的依赖项,而无需遍历每个依赖项的 pom 文件? (请原谅我的菜鸟,我是 Java 处女!)
    • @beterthanlife 最好就此提出一个新问题 :)
    • 我通过查看 gradle 依赖关系图 (./gradlew ::dependencies) 发现了一个重复的 jar。所以我找到了将旧版本的lib引入项目的罪魁祸首。
    【解决方案6】:

    在我的情况下,当我在部署在 Websphere 8.5 上的应用程序中添加 com.nimbusds 库时出现错误。
    发生以下异常:

    原因:java.lang.IncompatibleClassChangeError: org.objectweb.asm.AnnotationVisitor

    解决方案是从库中排除 asm jar:

    <dependency>
        <groupId>com.nimbusds</groupId>
        <artifactId>nimbus-jose-jwt</artifactId>
        <version>5.1</version>
        <exclusions>
            <exclusion>
                <artifactId>asm</artifactId>
                <groupId>org.ow2.asm</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    

    【讨论】:

      【解决方案7】:

      可能出现此错误的另一种情况是 Emma 代码覆盖率。

      将对象分配给接口时会发生这种情况。我猜这与被检测的对象有关,不再兼容二进制。

      http://sourceforge.net/tracker/?func=detail&aid=3178921&group_id=177969&atid=883351

      幸运的是,Cobertura 不会出现这个问题,所以我在 pom.xml 的报告插件中添加了 cobertura-maven-plugin

      【讨论】:

        【解决方案8】:

        我在取消部署和重新部署与 glassfish 的战争时遇到了这个问题。我的班级结构是这样的,

        public interface A{
        }
        
        public class AImpl implements A{
        }
        

        后来改成了

        public abstract class A{
        }
        
        public class AImpl extends A{
        }
        

        停止并重新启动域后,一切正常。 我使用的是 glassfish 3.1.43

        【讨论】:

          【解决方案9】:

          我有一个 Web 应用程序可以完美地部署在我本地机器的 tomcat(8.0.20) 上。但是,当我将它放入 qa 环境(tomcat - 8.0.20)时,它不断给我 IncompatibleClassChangeError 并且它抱怨我正在扩展接口。此接口已更改为抽象类。我编译了父类和子类,但我仍然遇到同样的问题。最后,我想调试,所以,我将父版本更改为 x.0.1-SNAPSHOT,然后编译所有内容,现在它可以工作了。如果有人在按照此处给出的答案后仍然遇到问题,请确保您的 pom.xml 中的版本也是正确的。更改版本以查看是否有效。如果是,则修复版本问题。

          【讨论】:

            【解决方案10】:

            我相信,我的回答将针对 Intellij。

            我已经重建干净,甚至手动删除“out”和“target”目录。 Intellij 有一个“使缓存无效并重新启动”,有时可以清除奇怪的错误。这一次没有奏效。依赖版本在项目设置->模块菜单中看起来都是正确的。

            最终的答案是从我的本地 maven repo 中手动删除我的问题依赖项。一个旧版本的 bouncycastle 是罪魁祸首(我知道我刚刚更改了版本,这就是问题所在),虽然旧版本没有出现在正在构建的地方,但它解决了我的问题。我使用的是 intellij 版本 14,然后在此过程中升级到 15。

            【讨论】:

              【解决方案11】:

              就我而言,我以这种方式遇到了这个错误。我的项目的pom.xml 定义了两个依赖项ABAB 都定义了对同一工件的依赖(称为 C),但它的不同版本(C.1C.2)。发生这种情况时,对于C 中的每个类,maven 只能从两个版本中选择该类的一个版本(同时构建uber-jar)。它将根据其dependency mediation 规则选择“最近”版本,并输出警告"We have a duplicate class..." 如果版本之间的方法/类签名发生更改,如果在运行时使用了不正确的版本,可能会导致java.lang.IncompatibleClassChangeError 异常.

              高级:如果A必须使用C的v1和B必须使用C的v2,那么我们必须在AB的poms中使用relocateC来避免构建依赖于AB 的最终项目时发生类冲突(我们有一个重复的类警告)。

              【讨论】:

                【解决方案12】:

                请检查您的代码是否不包含两个具有相同类名和包定义的模块项目。例如,如果有人使用复制粘贴来创建基​​于先前实现的接口的新实现,就会发生这种情况。

                【讨论】:

                  【解决方案13】:

                  如果这是此错误可能发生的记录,则:

                  我刚刚在 WAS (8.5.0.1) 上收到此错误,在 CXF (2.6.0) 加载 spring (3.1.1_release) 配置期间,BeanInstantiationException 卷起 CXF ExtensionException,卷起 IncompatibleClassChangeError。以下 sn-p 显示了堆栈跟踪的要点:

                  Caused by: org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [org.apache.cxf.bus.spring.SpringBus]: Constructor threw exception; nested exception is org.apache.cxf.bus.extension.ExtensionException
                              at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:162)
                              at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:76)
                              at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:990)
                              ... 116 more
                  Caused by: org.apache.cxf.bus.extension.ExtensionException
                              at org.apache.cxf.bus.extension.Extension.tryClass(Extension.java:167)
                              at org.apache.cxf.bus.extension.Extension.getClassObject(Extension.java:179)
                              at org.apache.cxf.bus.extension.ExtensionManagerImpl.activateAllByType(ExtensionManagerImpl.java:138)
                              at org.apache.cxf.bus.extension.ExtensionManagerBus.<init>(ExtensionManagerBus.java:131)
                              [etc...]
                              at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:147)
                              ... 118 more
                  
                  Caused by: java.lang.IncompatibleClassChangeError: 
                  org.apache.neethi.AssertionBuilderFactory
                              at java.lang.ClassLoader.defineClassImpl(Native Method)
                              at java.lang.ClassLoader.defineClass(ClassLoader.java:284)
                              [etc...]
                              at com.ibm.ws.classloader.CompoundClassLoader.loadClass(CompoundClassLoader.java:586)
                              at java.lang.ClassLoader.loadClass(ClassLoader.java:658)
                              at org.apache.cxf.bus.extension.Extension.tryClass(Extension.java:163)
                              ... 128 more
                  

                  在这种情况下,解决方案是在我的 war 文件中更改模块的类路径顺序。也就是说,在 WAS 控制台下打开 war 应用程序并选择客户端模块。在模块配置中,将class-loading设置为“parent last”。

                  这可以在 WAS 控制台中找到:

                  • 应用程序 -> 应用程序类型 -> WebSphere 企业应用程序
                  • 单击代表您的应用程序的链接(战争)
                  • 点击“模块”部分下的“管理模块”
                  • 点击底层模块的链接
                  • 将“类加载器顺序”更改为“(父级最后)”。

                  【讨论】:

                    【解决方案14】:

                    在花费太多时间后记录另一个场景。

                    确保您的依赖项 jar 中没有带有 EJB 注释的类。

                    我们有一个带有@local 注释的通用 jar 文件。该类后来从那个公共项目中移出并进入我们的主 ejb jar 项目。我们的 ejb jar 和我们的普通 jar 都捆绑在一个耳朵里。我们常见的 jar 依赖的版本没有更新。因此 2 个类试图成为具有不兼容更改的东西。

                    【讨论】:

                      【解决方案15】:

                      以上所有 - 无论出于何种原因,我都在进行一些重大的重构并开始得到这个。我重命名了我的界面所在的包并清除了它。希望对您有所帮助。

                      【讨论】:

                        【解决方案16】:

                        由于某种原因,在使用 JNI 并在调用 Call*Method() 时传递 jclass 参数而不是 jobject 时也会引发相同的异常。

                        这类似于 Ogre Psalm33 的答案。

                        void example(JNIEnv *env, jobject inJavaList) {
                            jclass class_List = env->FindClass("java/util/List");
                        
                            jmethodID method_size = env->GetMethodID(class_List, "size", "()I");
                            long size = env->CallIntMethod(class_List, method_size); // should be passing 'inJavaList' instead of 'class_List'
                        
                            std::cout << "LIST SIZE " << size << std::endl;
                        }
                        

                        我知道在被问到 5 年后回答这个问题有点晚,但这是搜索 java.lang.IncompatibleClassChangeError 时最热门的问题之一,所以我想记录这个特殊情况。

                        【讨论】:

                          【解决方案17】:

                          添加我的 2 美分。如果您使用 scala 和 sbt 和 scala-logging 作为依赖项;那么这可能会发生,因为 scala-logging 的早期版本具有名称 scala-logging-api.So;基本上不会发生依赖项解析因为在启动 scala 应用程序时不同的名称会导致运行时错误。

                          【讨论】:

                            【解决方案18】:

                            此问题的另一个原因是您是否为 Android Studio 启用了Instant Run

                            修复

                            如果您发现您开始收到此错误,请关闭 Instant Run

                            1. Android Studio 主要设置
                            2. 构建、执行、部署
                            3. 即时运行
                            4. 取消勾选“启用即时运行...”

                            为什么

                            Instant Run 在开发过程中修改了大量内容,以便更快地为正在运行的应用程序提供更新。因此即时运行。当它起作用时,它真的很有用。但是,当出现此类问题时,最好的办法是关闭Instant Run,直到下一个版本的 Android Studio 发布。

                            【讨论】:

                              猜你喜欢
                              • 2013-03-10
                              • 2011-03-12
                              • 1970-01-01
                              相关资源
                              最近更新 更多