java.lang.IncompatibleClassChangeError의 원인은 무엇입니까?


218

Java 라이브러리를 JAR로 패키징하고 있으며 java.lang.IncompatibleClassChangeError메소드를 호출하려고 할 때 많은 것을 던지고 있습니다. 이 오류는 무작위로 나타납니다. 이 오류의 원인은 무엇입니까?


필자는 Apache FOP 1.0 및 Barcode4J를 테스트하는 Eclipse 프로젝트에서 Barcode4J와 함께 제공되는 추가 라이브러리가 FOP와 함께 제공되는 라이브러리를 대체 한 것으로 보입니다 (일부는 더 높은 버전 번호를 가짐). 빌드 경로 / 클래스 경로에 넣는 것을 매우 조심스럽게 다루는 경우입니다.
Wivani

답변:


170

이것은 클라이언트 코드를 다시 컴파일하지 않고 라이브러리에 대해 호환되지 않는 바이너리 변경을 수행했음을 의미합니다. Java Language Specification §13 은 이러한 모든 변경 사항을 가장 두드러지게 설명하며, 비 static개인 필드 / 방법을 변경 static하거나 그 반대로 변경합니다.

새 라이브러리에 대해 클라이언트 코드를 다시 컴파일하십시오.

업데이트 : 공용 라이브러리를 게시하는 경우 "이진 역 호환성"이라는 것을 유지하기 위해 호환되지 않는 이진 변경을 최대한 피해야합니다. 의존성 jar 만 업데이트하는 것이 이상적으로 응용 프로그램이나 빌드를 중단해서는 안됩니다. 이진 역 호환성을 중단 해야하는 경우 변경 사항을 릴리스하기 전에 주 버전 번호 (예 : 1.xy에서 2.0.0으로)를 늘리는 것이 좋습니다 .


2
어떤 이유로 여기의 개발자는 클라이언트 코드를 다시 컴파일해도 문제가 정확하게 해결되지 않는 문제가 있습니다. 어떤 이유로 파일이 발생한 곳에서 파일을 편집하고 다시 컴파일하면 더 이상 오류가 발생하지 않지만 라이브러리가 참조되는 프로젝트의 다른 곳에서 더 많은 팝업이 무작위로 나타납니다. 나는 이것이 무엇을 일으킬 수 있는지 궁금합니다.
좀비

5
깨끗한 빌드 (모든 *.class파일 삭제 ) 및 재 컴파일 을 시도 했습니까 ? 파일을 편집하면 비슷한 효과가 있습니다.
notnoop

JSP를 고려하지 않는 한 동적으로 생성 된 코드는 없습니다. 우리는 클래스 파일을 삭제하려고 시도했지만 도움이되지 않는 것 같습니다. 이상한 것은 그것이 나에게는 일어나지 않는 것 같지만 다른 개발자에게는 발생한다는 것입니다.
좀비

2
깨끗한 빌드를 할 때 실행하는 것과 동일한 항아리에 대해 컴파일하고 있는지 확인할 수 있습니까?
notnoop

32 비트 또는 64 비트 플랫폼과 관련이 있습니까? 64 비트 플랫폼에서만 오류가 발생한다는 것을 알았습니다.
cowboi-peng

96

새로 패키지 된 라이브러리 이전 버전의 BC ( Backward Binary Compatible)가 아닙니다 . 이러한 이유로 재 컴파일되지 않은 라이브러리 클라이언트 중 일부는 예외를 발생시킬 수 있습니다.

이전 버전의 라이브러리로 빌드 한 클라이언트가 java.lang을 발생시킬 수있는 Java 라이브러리 API 의 전체 변경 사항 목록입니다. 그들이 새로운 것으로 실행될 때 (즉, BC를 깨는 경우) IncompatibleClassChangeError :

  1. 최종 필드가 아닌 정적 필드
  2. 일정하지 않은 필드는 고정되지 않습니다.
  3. 클래스가 인터페이스가되어
  4. 인터페이스가 클래스가되어
  5. 클래스 / 인터페이스에 새 필드를 추가하거나 새 수퍼 클래스 / 수퍼 인터페이스를 추가하면 클라이언트 클래스 C의 수퍼 인터페이스에서 정적 필드가 추가 된 필드 (같은 이름으로)를 C의 슈퍼 클래스 (매우 드문 경우).

참고 : 다른 호환되지 않는 변경으로 인해 NoSuchFieldError , NoSuchMethodError , IllegalAccessError , InstantiationError , VerifyError , NoClassDefFoundErrorAbstractMethodError 등의 다른 많은 예외 가 있습니다 .

BC에 대한 더 좋은 논문은 Jim des Rivières가 작성한 "Evaving Java-based APIs 2 : Achieving API Binary Compatibility" 입니다.

이러한 변경 사항을 감지 하는 자동 도구 도 있습니다 .

라이브러리에 대한 japi 준수 검사기 사용법 :

japi-compliance-checker OLD.jar NEW.jar

clirr 도구의 사용법 :

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

행운을 빕니다!


59

이 답변이 모두 맞지만 문제를 해결하는 것이 더 어려운 경우가 많습니다. 일반적으로 클래스 경로에 대한 동일한 종속성의 두 가지 약간 다른 버전의 결과이며 거의 항상 클래스 경로에 대해 원래 컴파일 된 것과 다른 수퍼 클래스 또는 전 이적 클로저의 가져 오기가 다르지만 일반적으로 클래스에서 발생합니다. 인스턴스화 및 생성자 호출. (클래스 로딩 및 ctor 호출이 성공하면 얻을 수 NoSuchMethodException있습니다.)

동작이 무작위로 나타날 경우, 어떤 코드가 먼저 적중되었는지에 따라 다른 전이 종속성을로드하는 멀티 스레드 프로그램의 결과 일 수 있습니다.

이를 해결하려면 -verbose인수로 VM을 시작한 다음 예외 발생시로드 된 클래스를 확인하십시오. 당신은 놀라운 정보를 보게 될 것입니다. 예를 들어, 동일한 종속성 및 버전의 사본이 여러 개 있으면 예상치 않았거나 포함 된 것으로 알고 있으면 수락했을 것입니다.

Maven으로 중복 jar를 해결하는 것은 Maven (또는 SBT의 Dependency Graph Plugin ) 에서 maven-dependency-pluginmaven-enforcer-plugin을 조합 한 다음 해당 jar을 최상위 POM의 섹션에 추가하거나 가져온 종속성으로 수행하는 것이 가장 좋습니다. SBT의 요소 (종속성을 제거하기 위해).

행운을 빕니다!


5
장황한 주장은 어떤 항아리가 문제를 일으키는지를 정확히 지적하는 데 도움이되었습니다. 감사합니다
Virat Kadaru

6

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

1
힌트 주셔서 감사합니다. 잘못된 인스턴스 (this)가 제공된 Java 메소드를 호출 할 때도 동일한 문제가 발생했습니다.
sstn

5

나는 같은 문제가 있었고 나중에 응용 프로그램이 버전 6에서 컴파일되는 동안 Java 버전 1.4에서 응용 프로그램을 실행하고 있음을 알았습니다.

실제로, 그 이유는 중복 라이브러리가 있었기 때문입니다. 하나는 클래스 경로 내에 있고 다른 하나는 클래스 경로 내에있는 jar 파일에 포함되어 있습니다.


이것의 바닥에 어떻게 도착 했습니까? 나는 비슷한 문제가 있지만 어떤 의존성 라이브러리가 복제되고 있는지 추적하는 방법을 알지 못한다.
beterthanlife

@beterthanlife는 중복 된 클래스를 찾는 모든 jar 파일 내부를 검색하는 스크립트를 작성합니다 (정규화 된 이름, 즉 패키지 이름으로 검색) :)
Eng.Fouad

좋아, NetBeans와 maven-shade를 사용하여 중복되고있는 모든 클래스와 이들이 존재하는 jar 파일을 볼 수 있다고 생각합니다.이 jar 파일 중 많은 부분이 내 pom.xml에서 직접 참조하지 않았으므로 내 의존성에 포함됩니다. 각 종속성의 pom 파일을 거치지 않고 종속성을 포함하는 종속성을 찾는 쉬운 방법이 있습니까? (내 멍청한 놈을 용서해주세요, 저는 자바 처녀입니다!)
beterthanlife

@beterthanlife 다음에 관한 새로운 질문을하는 것이
좋습니다

gradle dependencies 그래프 (./gradlew : <name> : dependencies)를 보면 중복 항아리가 발견되었습니다. 그래서 이전 버전의 lib를 프로젝트에 끌어 들인 범인을 발견했습니다.
nyx

1

이 오류가 나타날 수있는 또 다른 상황은 Emma Code Coverage입니다.

이것은 객체를 인터페이스에 할당 할 때 발생합니다. 나는 이것이 계측되는 객체와 관련이 있고 더 이상 바이너리와 호환되지 않는다고 생각합니다.

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

다행히이 문제는 Cobertura에서 발생하지 않으므로 pom.xml의보고 플러그인에 cobertura-maven-plugin을 추가했습니다.


1

Glassfish와의 전쟁을 배포 취소하고 재배치 하면서이 문제에 직면했습니다. 수업 구조는 다음과 같습니다.

public interface A{
}

public class AImpl implements A{
}

그리고 그것은

public abstract class A{
}

public class AImpl extends A{
}

도메인을 중지했다가 다시 시작한 후에 문제가 해결되었습니다. 나는 glassfish 3.1.43을 사용하고 있었다


1

로컬 컴퓨터의 바람둥이 (8.0.20)에 완벽하게 배포되는 웹 응용 프로그램이 있습니다. 그러나 qa 환경 (tomcat-8.0.20)에 넣을 때 IncompatibleClassChangeError가 계속 발생하여 인터페이스에서 확장되고 있다고 불평했습니다. 이 인터페이스는 추상 클래스로 변경되었습니다. 그리고 부모와 자식 클래스를 편집했지만 여전히 같은 문제가 계속 발생했습니다. 마지막으로 디버깅을 원했기 때문에 부모의 버전을 x.0.1-SNAPSHOT으로 변경 한 다음 모든 것을 컴파일하면 작동합니다. 여기에 제공된 답변을 따른 후에도 여전히 문제가 발생하는 경우 pom.xml의 버전도 올바른지 확인하십시오. 작동하는지 확인하려면 버전을 변경하십시오. 그렇다면 버전 문제를 해결하십시오.


1

내 대답은 Intellij에 특정한 것입니다.

"out"및 "target"디렉토리를 수동으로 삭제하는 경우에도 깔끔하게 재구성했습니다. Intellij에는 "캐시 무효화 및 다시 시작"이있어 때때로 이상한 오류가 해결됩니다. 이번에는 작동하지 않았습니다. 프로젝트 버전-> 모듈 메뉴에서 종속성 버전이 모두 올바르게 보입니다.

마지막 대답은 로컬 maven 저장소에서 문제 종속성을 수동으로 삭제하는 것이 었습니다. bouncycastle의 오래된 버전은 범인이었습니다 (내가 방금 버전을 변경했으며 문제가 될 것임을 알고 있음). 이전 버전은 빌드중인 곳에서 아무 것도 나타나지 않았지만 내 문제를 해결했습니다. 나는 intellij 버전 14를 사용하고 있었고이 과정에서 15로 업그레이드했습니다.


1

내 경우에는이 방법 으로이 오류가 발생했습니다. pom.xml내 프로젝트의 두 종속성을 정의 A하고 B. 그리고 모두 AB같은 유물 (호출에 정의 된 종속 C)하지만 (다른 버전 C.1C.2). 이런 일이 발생하면 Cmaven의 각 클래스에 대해 두 버전에서 하나의 클래스 버전 만 선택할 수 있습니다 ( uber-jar 빌드 중 ). 의존성 중개 규칙에 따라 "가장 가까운"버전을 선택하고 " 우리는 중복 클래스가 있습니다 ..." 라는 경고를 출력합니다 . 버전간에 메소드 / 클래스 서명이 변경 java.lang.IncompatibleClassChangeError되면 잘못된 버전이있는 경우 예외 가 발생할 수 있습니다. 런타임에 사용됩니다.

고급 : 만약이 A의 v1을 사용해야 CB의 v2를 사용해야합니다 C우리가해야 재배치 CAB모두에 따라 최종 프로젝트를 빌드 할 때 피할 클래스 충돌의 리딩은 (우리가 중복 클래스 경고가) AB.


1

제 경우에는에 com.nimbusds배포 된 응용 프로그램 에서 라이브러리를 추가 할 때 오류가 발생했습니다 Websphere 8.5.
아래 예외가 발생했습니다.

원인 : 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>

0

코드가 클래스 이름과 패키지 정의가 동일한 두 개의 모듈 프로젝트로 구성되어 있지 않은지 확인하십시오. 예를 들어 누군가 복사-붙여 넣기를 사용하여 이전 구현을 기반으로 인터페이스의 새로운 구현을 만드는 경우에 발생할 수 있습니다.


0

이것이이 오류가 발생할 수있는 기록 인 경우 :

BeanInstantiationException이 CXF ExtensionException을 롤업하여 IncompatibleClassChangeError를 롤업 한 스프링 (3.1.1_release) 구성의 CXF (2.6.0)로드 중 WAS (8.5.0.1)에서이 오류가 발생했습니다. 다음 스 니펫은 스택 추적의 요점을 보여줍니다.

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 애플리케이션을 열고 클라이언트 모듈을 선택하십시오. 모듈 구성에서 클래스 로딩을 "부모 마지막"으로 설정하십시오.

이것은 WAS 콘솔에서 찾을 수 있습니다.

  • 응용 프로그램-> 응용 프로그램 유형-> WebSphere Enterprise 응용 프로그램
  • 애플리케이션을 나타내는 링크를 클릭하십시오 (전쟁)
  • "모듈"섹션에서 "모듈 관리"를 클릭하십시오.
  • 기본 모듈에 대한 링크를 클릭하십시오
  • "클래스 로더 순서"를 "(부모 마지막)"으로 변경하십시오.

0

너무 많은 시간을 레코딩 한 후 다른 시나리오를 문서화합니다.

EJB 어노테이션이있는 클래스가있는 종속성 jar이 없는지 확인하십시오.

@local주석 이있는 일반적인 jar 파일이있었습니다 . 그 수업은 나중에 일반적인 프로젝트에서 주요 ejb jar 프로젝트로 옮겨졌습니다. 우리의 ejb 병과 우리의 공통 병은 귀 안에 묶여 있습니다. 공통 jar 종속성 버전이 업데이트되지 않았습니다. 따라서 2 개의 클래스는 호환되지 않는 변경 사항을 가지려고합니다.


0

위의 모든 것-어떤 이유로 든 큰 리팩터링을하고 이것을 얻기 시작했습니다. 인터페이스가 포함 된 패키지의 이름을 바꾸어 정리했습니다. 희망이 도움이됩니다.


0

어떤 이유로 JNI를 사용하고을 호출 할 때 jobject 대신 jclass 인수를 전달할 때도 동일한 예외가 발생합니다 Call*Method().

이것은 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특별한 경우를 문서화하고 싶었다.


0

내 2 센트 추가. scala 및 sbt 및 scala-logging을 종속성으로 사용하는 경우 scala-logging의 이전 버전에는 scala-logging-api라는 이름이 있었기 때문에 발생할 수 있습니다. 스칼라 응용 프로그램을 시작하는 동안 런타임 오류가 발생하는 이름.


0

이 문제의 또 다른 원인은 Instant RunAndroid Studio를 사용하도록 설정 한 경우 입니다.

수정

이 오류가 발생하면를 끄십시오 Instant Run.

  1. Android Studio 기본 설정
  2. 빌드, 실행, 배포
  3. 즉시 실행
  4. "즉석 실행 활성화 ..."

Instant Run개발하는 동안 많은 것들을 수정하여 실행중인 앱에 더 빨리 업데이트를 제공합니다. 따라서 즉시 실행됩니다. 작동하면 정말 유용합니다. 그러나 이와 같은 문제가 발생 Instant Run하면 다음 버전의 Android Studio가 출시 될 때까지 끄는 것이 가장 좋습니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.