Was verursacht java.lang.IncompatibleClassChangeError?

218

Ich packe eine Java-Bibliothek als JAR und es werden viele java.lang.IncompatibleClassChangeErrors ausgelöst, wenn ich versuche, Methoden daraus aufzurufen. Diese Fehler scheinen zufällig zu erscheinen. Welche Probleme könnten diesen Fehler verursachen?

Zombies
quelle
In einem Eclipse-Projekt, in dem ich Apache FOP 1.0 und Barcode4J getestet habe, haben die zusätzlichen Bibliotheken, die mit Barcode4J geliefert wurden, anscheinend die mit FOP gelieferten Bibliotheken überschrieben (einige hatten höhere Versionsnummern). Es ist ein Grund, sehr vorsichtig zu sein, was Sie in Ihren Erstellungspfad / Klassenpfad einfügen.
Wivani

Antworten:

170

Dies bedeutet, dass Sie einige inkompatible binäre Änderungen an der Bibliothek vorgenommen haben, ohne den Clientcode neu zu kompilieren. Java-Sprachspezifikation §13 beschreibt alle derartigen Änderungen, vor allem, indem staticnicht-nicht-private Felder / Methoden geändert werden staticoder umgekehrt.

Kompilieren Sie den Client-Code erneut mit der neuen Bibliothek, und Sie sollten bereit sein.

UPDATE: Wenn Sie eine öffentliche Bibliothek veröffentlichen, sollten Sie inkompatible binäre Änderungen so weit wie möglich vermeiden, um die sogenannte "binäre Abwärtskompatibilität" beizubehalten. Das Aktualisieren von Abhängigkeitsgläsern allein sollte im Idealfall weder die Anwendung noch den Build beschädigen. Wenn Sie die binäre Abwärtskompatibilität unterbrechen müssen, wird empfohlen , die Hauptversionsnummer (z. B. von 1.xy auf 2.0.0) zu erhöhen, bevor Sie die Änderung freigeben.

notnoop
quelle
2
Aus irgendeinem Grund haben die Entwickler hier ein Problem, bei dem das Neukompilieren des Client-Codes das Problem nicht genau behebt. Wenn sie die Datei dort bearbeiten, wo sie auftritt, und den Fehler neu kompilieren, tritt der Fehler aus irgendeinem Grund nicht mehr dort auf, sondern es werden zufällig weitere an anderer Stelle im Projekt angezeigt, auf die die Bibliothek verweist. Ich bin gespannt, was dies möglicherweise bewirken könnte.
Zombies
5
Haben Sie versucht, einen sauberen Build durchzuführen (alle *.classDateien löschen ) und neu zu kompilieren? Das Bearbeiten von Dateien hat einen ähnlichen Effekt.
Notnoop
Kein dynamisch generierter Code ... es sei denn, Sie würden eine JSP als solche betrachten. Wir haben versucht, die Klassendateien zu löschen, und dies schien nicht zu helfen. Das Seltsame ist, dass es für mich einfach nicht zu passieren scheint, aber für andere Entwickler.
Zombies
2
Können Sie sicherstellen, dass Sie bei einem sauberen Build gegen dieselben Jars kompilieren, mit denen Sie arbeiten?
Notnoop
Gibt es etwas mit 32-Bit- oder 64-Bit-Plattform zu tun, fand ich einen Fehler nur in 64-Bit-Plattform durchführen.
Cowboi-Peng
96

Ihre neu gepackte Bibliothek ist nicht abwärtsbinärkompatibel (BC) mit der alten Version. Aus diesem Grund können einige der nicht neu kompilierten Bibliotheksclients die Ausnahme auslösen.

Dies ist eine vollständige Liste der Änderungen in der Java-Bibliotheks-API, die dazu führen können, dass Clients, die mit einer alten Version der Bibliothek erstellt wurden, java.lang auslösen. IncompatibleClassChangeError, wenn sie auf einem neuen ausgeführt werden (dh BC brechen):

  1. Nicht endgültiges Feld wird statisch,
  2. Nicht konstantes Feld wird nicht statisch,
  3. Klasse wird Schnittstelle,
  4. Schnittstelle wird Klasse,
  5. Wenn Sie der Klasse / Schnittstelle ein neues Feld hinzufügen (oder eine neue Superklasse / Superschnittstelle hinzufügen), kann ein statisches Feld von einer Superschnittstelle einer Clientklasse C ein hinzugefügtes Feld (mit demselben Namen) ausblenden, das von der geerbt wurde Superklasse von C (sehr seltener Fall).

Hinweis : Es gibt viele andere Ausnahmen, die durch andere inkompatible Änderungen verursacht werden: NoSuchFieldError , NoSuchMethodError , IllegalAccessError , InstantiationError , VerifyError , NoClassDefFoundError und AbstractMethodError .

Das bessere Papier über BC ist "Evolving Java-based APIs 2: Erreichen der binären API-Kompatibilität", geschrieben von Jim des Rivières.

Es gibt auch einige automatische Tools , um solche Änderungen zu erkennen:

Verwendung des Japi-Compliance-Checkers für Ihre Bibliothek:

japi-compliance-checker OLD.jar NEW.jar

Verwendung des Clirr-Werkzeugs:

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

Viel Glück!

Linuxbuild
quelle
59

Obwohl diese Antworten alle richtig sind, ist die Lösung des Problems oft schwieriger. Dies ist im Allgemeinen das Ergebnis von zwei leicht unterschiedlichen Versionen derselben Abhängigkeit vom Klassenpfad und wird fast immer entweder durch eine andere Oberklasse als ursprünglich kompiliert, die ursprünglich gegen das Befinden im Klassenpfad kompiliert wurde, oder durch einen unterschiedlichen Import des transitiven Abschlusses, jedoch im Allgemeinen in der Klasse Instanziierung und Konstruktoraufruf. (Nach erfolgreichem Laden der Klasse und ctor-Aufruf erhalten Sie NoSuchMethodExceptionoder so weiter.)

Wenn das Verhalten zufällig erscheint, ist es wahrscheinlich das Ergebnis eines Multithread-Programms, das verschiedene transitive Abhängigkeiten lädt, je nachdem, welcher Code zuerst getroffen wurde.

Um diese Probleme zu beheben, starten Sie die VM mit -verboseeinem Argument und sehen Sie sich dann die Klassen an, die geladen wurden, als die Ausnahme auftrat. Sie sollten einige überraschende Informationen sehen. Wenn Sie beispielsweise mehrere Kopien derselben Abhängigkeit und Versionen haben, die Sie nie erwartet hätten oder die Sie akzeptiert hätten, wenn Sie gewusst hätten, dass sie enthalten sind.

Beheben doppelte Gläser mit Maven wird am besten mit einer Kombination aus der getan Maven-Abhängigkeit-Plugin und maven-Enforcer-Plugin unter Maven (oder die SBT Dependency Graph Plugin , dann die Gläser auf einen Abschnitt Ihres Top-Level POM oder als importierte Abhängigkeit Zugabe Elemente in SBT (um diese Abhängigkeiten zu entfernen).

Viel Glück!

Brian Topping
quelle
5
Das ausführliche Argument half mir herauszufinden, welche Gläser Probleme bereiteten. Vielen Dank
Virat Kadaru
6

Ich habe auch festgestellt, dass bei Verwendung von JNI beim Aufrufen einer Java-Methode aus C ++, wenn Sie Parameter in der falschen Reihenfolge an die aufgerufene Java-Methode übergeben, dieser Fehler angezeigt wird, wenn Sie versuchen, die Parameter in der aufgerufenen Methode zu verwenden (weil sie wird nicht der richtige Typ sein). Ich war anfangs überrascht, dass JNI diese Prüfung nicht als Teil der Klassensignaturprüfung für Sie durchführt, wenn Sie die Methode aufrufen, aber ich gehe davon aus, dass sie diese Art der Prüfung nicht durchführen, weil Sie möglicherweise polymorphe Parameter übergeben und dies müssen Angenommen, Sie wissen, was Sie tun.

Beispiel C ++ JNI Code:

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.
}

Beispiel Java Code:

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...
        }
    }
}
Oger Psalm33
quelle
1
Danke für den Tipp. Ich hatte das gleiche Problem beim Aufrufen einer Java-Methode mit der falschen Instanz (diese).
Sstn
5

Ich hatte das gleiche Problem und später stellte ich fest, dass ich die Anwendung auf Java Version 1.4 ausführe, während die Anwendung auf Version 6 kompiliert wird.

Der Grund war, dass eine doppelte Bibliothek vorhanden war, eine im Klassenpfad und die andere in einer JAR-Datei, die sich im Klassenpfad befindet.

Eng.Fouad
quelle
Wie sind Sie dem auf den Grund gegangen? Ich bin mir sicher, dass ich ein ähnliches Problem habe, aber keine Ahnung habe, wie ich verfolgen kann, welche Abhängigkeitsbibliothek dupliziert wird.
beterthanlife
@beterthanlife schreiben ein Skript, das in allen JAR-Dateien nach doppelten Klassen sucht (Suche nach ihrem
vollqualifizierten
ok, mit NetBeans und Maven-Shade kann ich wahrscheinlich alle Klassen sehen, die dupliziert werden, und die JAR-Dateien, in denen sie sich befinden. Viele dieser JAR-Dateien, auf die ich in meiner pom.xml nicht direkt verwiesen habe, gehen davon aus, dass sie es müssen von meinen Abhängigkeiten einbezogen werden. Gibt es eine einfache Möglichkeit, herauszufinden, welche Abhängigkeit sie enthält, ohne die POM-Dateien der einzelnen Abhängigkeiten durchzugehen? (Bitte verzeihen Sie meine Noobishness, ich bin eine Java-Jungfrau!)
beterthanlife
@beterthanlife Stellen Sie besser eine neue Frage dazu :)
Eng.Fouad
Ich habe ein doppeltes Glas gefunden, indem ich mir das Diagramm mit den Gradle-Abhängigkeiten angesehen habe (./gradlew: <Name>: Abhängigkeiten). Also habe ich den Schuldigen gefunden, der die alte Version der Bibliothek in das Projekt hineingezogen hat.
Nyx
1

Eine andere Situation, in der dieser Fehler auftreten kann, ist Emma Code Coverage.

Dies geschieht, wenn einer Schnittstelle ein Objekt zugewiesen wird. Ich denke, das hat etwas damit zu tun, dass das Objekt instrumentiert und nicht mehr binär kompatibel ist.

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

Glücklicherweise tritt dieses Problem bei Cobertura nicht auf, daher habe ich das Cobertura-Maven-Plugin in meinen Berichts-Plugins meiner pom.xml hinzugefügt

stivlo
quelle
1

Ich habe mich diesem Problem gestellt, als ich einen Krieg mit Glasfischen aufgehoben und wieder eingesetzt habe. Meine Klassenstruktur war so,

public interface A{
}

public class AImpl implements A{
}

und es wurde geändert zu

public abstract class A{
}

public class AImpl extends A{
}

Nach dem Stoppen und Neustarten der Domain hat es gut funktioniert. Ich habe Glassfish 3.1.43 verwendet

Nerrve
quelle
1

Ich habe eine Webanwendung, die problemlos auf dem Tomcat meines lokalen Computers (8.0.20) bereitgestellt werden kann. Als ich es jedoch in die QA-Umgebung (Tomcat - 8.0.20) einfügte, gab es mir weiterhin den IncompatibleClassChangeError und es beschwerte sich, dass ich eine Schnittstelle erweiterte. Diese Schnittstelle wurde in eine abstrakte Klasse geändert. Und ich habe die Eltern- und Kinderklassen zusammengestellt und trotzdem immer wieder das gleiche Thema bekommen. Schließlich wollte ich debuggen, also habe ich die Version auf dem übergeordneten Element in x.0.1-SNAPSHOT geändert und dann alles kompiliert und jetzt funktioniert es. Wenn das Problem nach den hier angegebenen Antworten immer noch auftritt, stellen Sie bitte sicher, dass auch die Versionen in Ihrer pom.xml korrekt sind. Ändern Sie die Versionen, um festzustellen, ob dies funktioniert. Wenn ja, beheben Sie das Versionsproblem.

Jobin Thomas
quelle
1

Ich glaube, meine Antwort wird Intellij-spezifisch sein.

Ich hatte sauber neu aufgebaut und ging sogar so weit, die Verzeichnisse "out" und "target" manuell zu löschen. Intellij hat einen "Cache ungültig machen und neu starten", der manchmal ungerade Fehler löscht. Diesmal hat es nicht funktioniert. Die Abhängigkeitsversionen sahen im Menü Projekteinstellungen-> Module alle korrekt aus.

Die endgültige Antwort war, meine Problemabhängigkeit manuell aus meinem lokalen Maven-Repo zu löschen. Eine alte Version von Bouncycastle war der Schuldige (ich wusste, dass ich gerade die Versionen geändert hatte und das wäre das Problem) und obwohl die alte Version nirgends in dem, was gebaut wurde, auftauchte, löste sie mein Problem. Ich habe Intellij Version 14 verwendet und dann während dieses Vorgangs auf 15 aktualisiert.

David Nickerson
quelle
1

In meinem Fall bin ich auf diese Weise auf diesen Fehler gestoßen. pom.xmlvon meinem Projekt definiert zwei Abhängigkeiten Aund B. Und beide Aund Bdefinierte Abhängigkeit von demselben Artefakt (nennen wir es C), aber verschiedenen Versionen davon ( C.1und C.2). In diesem CFall kann für jede Klasse in Maven nur eine Version der Klasse aus den beiden Versionen ausgewählt werden (während ein Uber-Jar erstellt wird ). Es wählt die "nächste" Version basierend auf den Regeln für die Abhängigkeitsvermittlung aus und gibt eine Warnung "Wir haben eine doppelte Klasse ..." aus. Wenn sich eine Methoden- / Klassensignatur zwischen den Versionen ändert, kann dies eine java.lang.IncompatibleClassChangeErrorAusnahme verursachen, wenn die falsche Version vorliegt zur Laufzeit verwendet.

Advanced: Wenn Amuss v1 verwenden Cund Bmüssen v2 von verwenden C, dann müssen wir verlagern C in Aund B‚s Poms zu vermeiden Klassenkonflikt (wir haben eine doppelte Klasse Warnung) , wenn das endgültige Projekt Bau, der auf beiden abhängt Aund B.

Morpheus
quelle
1

In meinem Fall trat der Fehler auf, als ich die com.nimbusdsBibliothek in meiner Anwendung hinzugefügt habe, die am bereitgestellt wurde Websphere 8.5.
Die folgende Ausnahme ist aufgetreten:

Auslöser: java.lang.IncompatibleClassChangeError: org.objectweb.asm.AnnotationVisitor

Die Lösung bestand darin, das ASM-Glas aus der Bibliothek auszuschließen:

<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>
Amin
quelle
0

Bitte überprüfen Sie, ob Ihr Code nicht aus zwei Modulprojekten besteht, die dieselben Klassennamen und Paketdefinitionen haben. Dies kann beispielsweise passieren, wenn jemand Copy-Paste verwendet, um eine neue Implementierung der Schnittstelle basierend auf der vorherigen Implementierung zu erstellen.

denu
quelle
0

Wenn dies eine Aufzeichnung möglicher Vorkommen dieses Fehlers ist, dann:

Ich habe gerade diesen Fehler in WAS (8.5.0.1) während des CXF (2.6.0) -Ladens der Spring-Konfiguration (3.1.1_release) erhalten, bei dem eine BeanInstantiationException eine CXF ExtensionException und einen IncompatibleClassChangeError aufgerollt hat. Das folgende Snippet zeigt den Kern der Stapelverfolgung:

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

In diesem Fall bestand die Lösung darin, die Klassenpfadreihenfolge des Moduls in meiner Kriegsdatei zu ändern. Öffnen Sie also die Kriegsanwendung in der WAS-Konsole unter und wählen Sie die Client-Module aus. Setzen Sie in der Modulkonfiguration das Laden der Klasse auf "last last".

Dies befindet sich in der WAS-Konsole:

  • Anwendungen -> Anwendungstypen -> WebSphere Enterprise-Anwendungen
  • Klicken Sie auf den Link, der Ihre Anwendung darstellt (Krieg)
  • Klicken Sie im Abschnitt "Module" auf "Module verwalten"
  • Klicken Sie auf den Link für die zugrunde liegenden Module.
  • Ändern Sie "Class Loader Order" in "(parent last)".
wmorrison365
quelle
0

Ein anderes Szenario dokumentieren, nachdem viel zu viel Zeit gebrannt wurde.

Stellen Sie sicher, dass Sie kein Abhängigkeitsglas haben, das eine Klasse mit einer EJB-Annotation enthält.

Wir hatten eine gemeinsame JAR-Datei mit einer @localAnmerkung. Diese Klasse wurde später aus diesem gemeinsamen Projekt in unser Hauptprojekt ejb jar verschoben. Unser EJB-Glas und unser gemeinsames Glas sind beide in einem Ohr gebündelt. Die Version unserer allgemeinen JAR-Abhängigkeit wurde nicht aktualisiert. Also 2 Klassen, die versuchen, etwas mit inkompatiblen Änderungen zu sein.

Snekse
quelle
0

All das oben genannte - aus irgendeinem Grund habe ich einen großen Refactor gemacht und angefangen, das zu bekommen. Ich habe das Paket umbenannt, in dem sich meine Schnittstelle befand, und das hat es gelöscht. Hoffentlich hilft das.

bsautner
quelle
0

Aus irgendeinem Grund wird dieselbe Ausnahme auch ausgelöst, wenn JNI verwendet wird und beim Aufrufen von a das Argument jclass anstelle des Jobobjekts übergeben wird Call*Method() .

Dies ähnelt der Antwort von 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;
}

Ich weiß, dass es etwas spät ist, diese Frage 5 Jahre nach der Beantwortung zu beantworten, aber dies ist einer der Top-Hits bei der Suche, java.lang.IncompatibleClassChangeErrordeshalb wollte ich diesen Sonderfall dokumentieren.

Eric Obermühlner
quelle
0

Hinzufügen meiner 2 Cent. Wenn Sie scala und sbt und scala-logging als Abhängigkeit verwenden, kann dies passieren, weil die frühere Version von scala-logging den Namen scala-logging-api hatte. Im Wesentlichen passieren die Abhängigkeitsauflösungen nicht aufgrund unterschiedlicher Namen, die beim Starten der Scala-Anwendung zu Laufzeitfehlern führen.

Sourabh
quelle
0

Eine weitere Ursache für dieses Problem ist, wenn Sie Instant RunAndroid Studio aktiviert haben .

Die Reparatur

Wenn Sie feststellen, dass dieser Fehler auftritt, schalten Sie ihn aus Instant Run.

  1. Android Studio Haupteinstellungen
  2. Erstellen, Ausführen, Bereitstellen
  3. Sofortiger Lauf
  4. Deaktivieren Sie "Sofortige Ausführung aktivieren ..."

Warum

Instant RunÄndert eine große Anzahl von Dingen während der Entwicklung, um die Bereitstellung von Updates für Ihre laufende App zu beschleunigen. Daher sofort laufen. Wenn es funktioniert, ist es wirklich nützlich. Wenn jedoch ein Problem wie dieses auftritt, sollten Sie es am besten Instant Runbis zur nächsten Version von Android Studio deaktivieren.

Knossos
quelle