什么原因导致java.lang.IncompatibleClassChangeError?

我将Java库打包为JAR,当我试图从它调用方法时,它抛出许多__abc 0。这些错误似乎是随机出现的。什么样的问题会导致这个错误?

344497 次浏览

这意味着您已经对库进行了一些不兼容的二进制更改,而无需重新编译客户端代码。Java语言规范§13详细描述了所有此类更改,最突出的是将非-static的非私有字段/方法更改为static,反之亦然。

根据新的库重新编译客户端代码,应该就可以开始了。

更新:如果你发布了一个公共库,你应该尽可能避免做出不兼容的二进制更改,以保持所谓的“二进制向后兼容性”。单独更新依赖jar在理想情况下不会破坏应用程序或构建。如果必须打破二进制向后兼容性,则使用推荐来增加主版本号(例如从1.x。Y到2.0.0),然后发布更改。

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

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

  1. 非终场变为静态,
  2. 非常数场变成非静态场,
  3. 类变成接口,
  4. 接口变成类,
  5. 如果你添加了一个新的字段到类/接口(或者添加了一个新的超类/超接口),那么一个来自客户端类C的超接口的静态字段可能会隐藏一个从C的超类继承的添加的字段(具有相同的名称)(非常罕见的情况)。

请注意:有许多由其他不兼容的变化引起的其他异常: NoSuchFieldErrorNoSuchMethodErrorIllegalAccessErrorInstantiationErrorVerifyErrorNoClassDefFoundErrorAbstractMethodError

关于BC更好的论文是《进化基于java的API 2:实现API二进制兼容性》写的吉姆des Rivières。

还有一些自动化工具来检测这样的变化:

在你的库中使用日语遵从检查器:

japi-compliance-checker OLD.jar NEW.jar

clir工具的使用:

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

好运!

我还发现,当使用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...
}
}
}

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

如果这种行为看起来是随机的,那么它很可能是多线程程序类根据最先遇到的代码加载不同的传递依赖项的结果。

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

用Maven解决重复的jar最好是结合Maven下的maven-dependency-pluginmaven-enforcer-plugin(或SBT的依赖图插件),然后将这些jar添加到顶级POM的一部分或作为SBT中的导入依赖项(以删除这些依赖项)。

好运!

另一种可能出现此错误的情况是Emma Code Coverage。

这发生在将Object分配给接口时。我猜这与对象被检测和不再二进制兼容有关。

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

幸运的是,这个问题在Cobertura中没有发生,所以我在pom.xml的报告插件中添加了Cobertura -maven-plugin

我在与玻璃鱼的战争中反复部署时遇到过这个问题。我的班级结构是这样的,

public interface A{
}


public class AImpl implements A{
}

它被改成了

public abstract class A{
}


public class AImpl extends A{
}

在停止并重新启动域之后,它工作得很好。 我使用的是glassfish 3.1.43

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

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

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

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

我刚刚在WAS(8.5.0.1)上得到了这个错误,在CXF(2.6.0)加载spring (3.1.1_release)配置期间,BeanInstantiationException卷起一个CXF ExtensionException,卷起一个IncompatibleClassChangeError。下面的代码片段显示了堆栈跟踪的要点:

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控制台中打开战争应用程序,并选择客户端模块。在模块配置中,将类加载设置为“parent last”。

在WAS控制台中可以找到:

  • 应用程序->应用程序类型-> WebSphere企业应用程序
  • 单击代表应用程序的链接(war)
  • 点击“模块”区域下的“模块管理”
  • 单击底层模块的链接
  • 将“类装入器顺序”更改为“(父类最后)”。

在浪费了太多时间后记录下另一个场景。

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

我们有一个普通的jar文件,它有一个@local注释。这个类后来从这个公共项目移到我们的主ejb jar项目中。ejb jar和公共jar都捆绑在ear中。我们的公共jar依赖项的版本没有更新。因此,两个类试图成为具有不兼容的更改的东西。

所有以上-无论出于什么原因,我做了一些大的重构,开始得到这个。我重命名了我的接口所在的包,这就清除了它。希望这能有所帮助。

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

我的答案,我相信,将是Intellij具体。

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

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

在我的例子中,我是这样遇到这个错误的。我的项目的pom.xml定义了两个依赖ABAB都定义了对同一工件(称为C)的依赖,但它的不同版本(C.1C.2)。当这种情况发生时,对于C maven中的每个类,只能从两个版本中选择一个版本(同时构建A0)。它将根据其A1规则选择“最近的”版本,并将输出警告A2。如果方法/类的签名在版本之间发生变化,如果在运行时使用了不正确的版本,则可能导致java.lang.IncompatibleClassChangeError异常。

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

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

这与《食人魔诗篇》第33篇中的回答相似。

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时最热门的问题之一,所以我想记录这个特殊情况。

如果你使用scala和sbt和scala-logging作为依赖项,那么这可能会发生,因为scala-logging的早期版本的名称是scala-logging-api.因此,本质上依赖项解析不会发生,因为不同的名称会导致启动scala应用程序时的运行时错误。

这个问题的另一个原因是,如果你为Android Studio启用了Instant Run

修复

如果你发现你开始得到这个错误,关闭Instant Run

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

为什么

Instant Run在开发过程中修改了大量的东西,以使它更快地为运行中的应用程序提供更新。因此,立即运行。当它起作用时,它真的很有用。然而,当出现这样的问题时,最好的方法是关闭Instant Run,直到Android Studio发布下一个版本。

在我的例子中,当我在部署在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>

我得到这个错误是因为我有一个抽象基类,它承诺实现某个接口,但我忘记了添加接口方法的实现,然后我创建了一个非抽象(具体)字节码生成的类,它扩展了抽象类,也没有提供这些方法的实现。

当我尝试创建字节码生成类的实例时,JVM抱怨java.lang.IncompatibleClassChangeError

幸运的是,这个例外有一个“消息”;成员,它提供了有关错误的更详细信息。在我的例子中,消息清楚地说,特定的类应该实现特定的接口,但它实际上并没有实现它。

如果你从事android开发。然后尝试rebuild选项可能会为你修复。