HOWTO · Java

Java.Lang.NoClassDefFoundError 수정: 클래스 오류를 초기화할 수 없음

이 자습서에서는 java.lang.noclassdeffounderror: 클래스 오류를 초기화할 수 없음을 보여주고 가능한 이유에 대해 설명하고 솔루션을 제공합니다.

이 페이지의 내용

이 오류는 보통 같은 JVM에서 앞서 클래스의 정적 초기화에 실패했다는 뜻입니다. 최초로 클래스를 사용하는 과정에서는 흔히 java.lang.ExceptionInInitializerError가 보고됩니다. 이후 같은 클래스 로더에서 해당 클래스를 다시 사용하려 하면 java.lang.NoClassDefFoundError: Could not initialize class가 보고될 수 있습니다. 최초 실패를 찾아 해결하세요. 뒤에 나타나는 메시지는 원인이 아니라 그 결과인 경우가 많습니다.

이는 코드가 클래스 이름으로 클래스를 명시적으로 로드하려 했지만 찾지 못했다는 뜻인 java.lang.ClassNotFoundException와 다릅니다. 클래스 정의가 누락된 경우에도 다른 형태의 NoClassDefFoundError가 발생할 수 있으며, 보통 특정 클래스를 찾을 수 없다고 표시됩니다. “Could not initialize class”라는 문구는 초기화 상태를 가리킵니다. JAR을 추가하거나 클래스패스를 변경하는 방법은 최초 실패에서 누락된 종속성이 실제로 확인된 경우에만 도움이 됩니다.

NoClassDefFoundError: Could not initialize class의 의미

JVM이 클래스를 초기화할 때 정적 필드 초기화 코드와 정적 초기화 블록을 실행합니다. 이 과정이 실패하면 JVM은 해당 클래스 로더에서 그 클래스를 오류 상태로 표시합니다. 같은 로더에서 이후에 클래스를 사용해도 초기화 코드를 다시 실행할 수 없으므로 NoClassDefFoundError가 발생할 수 있습니다. 프로세스를 다시 시작하면 새로 초기화를 시도하지만, 근본적인 결함이 해결되는 것은 아닙니다.

흔한 유발 요인으로는 작업 디렉터리 기준 상대 경로에서 파일을 읽는 정적 초기화 코드, 잘못된 설정을 파싱하는 코드, 사용할 수 없는 서비스에 접근하는 코드, 실패하는 종속성 호출 등이 있습니다. 따라서 같은 코드가 개발자 컴퓨터에서는 작동하지만 작업 디렉터리, 파일 권한, 환경 변수 또는 종속성이 다른 서버에서는 실패할 수 있습니다. 이는 조사해 볼 가능성이지, 모든 오류의 원인을 포괄하는 목록은 아닙니다.

명시적인 애플리케이션 시작 단계에서 처리할 수 있다면 정적 초기화 중에 취약하거나 환경에 따라 달라지는 작업을 하지 마세요. 초기화가 실패해야 하는 상황이라면 원인을 보존하고 관련 설정이나 리소스를 파악할 수 있도록 충분한 맥락을 기록하세요.

정적 초기화 코드로 실패 재현하기

이 작은 프로그램은 최초 초기화 실패를 일으킨 다음 같은 클래스를 다시 사용합니다. 두 번째 시도는 뒤이어 발생하는 오류가 유용한 최초 예외를 가릴 수 있는 이유를 보여줍니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

public class InitializationFailure {
  static class Settings {
    static {
      if (Boolean.parseBoolean("true")) {
        throw new IllegalStateException("configuration is missing");
      }
    }

    static void load() {
      System.out.println("settings loaded");
    }
  }

  public static void main(String[] args) {
    try {
      Settings.load();
    } catch (Throwable error) {
      System.out.println(error.getClass().getName());
      if (error.getCause() != null) {
        System.out.println(error.getCause());
      }
    }

    try {
      Settings.load();
    } catch (Throwable error) {
      System.out.println(error.getClass().getName());
    }
  }
}

출력은 다음과 같습니다.

java.lang.ExceptionInInitializerError
java.lang.IllegalStateException: configuration is missing
java.lang.NoClassDefFoundError

첫 번째 호출은 초기화 실패와 그 원인을 보고합니다. 두 번째 호출에서는 Settings가 이미 이 JVM과 클래스 로더에서 실패한 상태로 표시되어 있습니다. 오류 메시지의 정확한 문구는 Java 구현에 따라 다를 수 있으므로, 이 예제는 특정 JVM의 메시지에 의존하지 않고 예외 클래스 이름과 첫 번째 원인을 출력합니다.

애플리케이션에서는 PropHolder와 같은 속성 보유 클래스가 정적 필드 초기화 중에 파일을 읽을 수 있습니다. 한 환경에서 파일이 없거나 읽을 수 없거나 형식이 잘못되면 최초 실패로 인해 해당 프로세스가 실행되는 동안 클래스가 오류 상태에 빠질 수 있습니다. 상대 경로는 프로세스의 작업 디렉터리를 기준으로 해석되며, 이 디렉터리는 IDE, 서비스 관리자, 컨테이너, 명령 셸에서 서로 다를 수 있습니다. 이를 확인할 만한 구체적인 패턴 중 하나로 살펴보고, 실제로 해당하는지는 스택 추적으로 판단하세요.

최초 초기화 실패 찾기

최종 NoClassDefFoundError만 보지 말고, 문제가 발생한 프로세스에서 해당 클래스에 대해 기록된 가장 이른 로그 항목부터 확인하세요. 전체 로그에서 ExceptionInInitializerError, 클래스 이름, 첫 번째 Caused by: 항목을 검색하세요. 최초 예외와 스택 프레임을 보면 실패한 초기화 코드와 실패한 작업을 대개 찾을 수 있습니다.

이 증거를 바탕으로 관련 입력을 확인하세요.

  • 파일을 읽는 경우, 문제가 발생한 환경에서 해석된 경로, 프로세스의 작업 디렉터리, 파일 존재 여부, 권한, 인코딩, 파일 내용을 확인하세요.
  • 설정을 파싱하는 경우, 해당 값을 살펴보고 파싱할 수 없었던 설정 항목을 기록하세요.
  • 종속성을 호출하는 경우, 종속성 자체의 원인 예외와 런타임 버전을 확인하고 시작 시 필요한 서비스를 사용할 수 있는지 살펴보세요.
  • 초기화 순서나 순환 참조가 의심되는 경우, 서로를 참조하는 정적 필드를 확인하고 아직 초기화되지 않은 상태에 의존하지 않도록 하세요.

“class”라는 단어만 보고 클래스패스 문제라고 단정하지 마세요. 최초 원인에 누락된 클래스나 메서드가 언급된 경우에만 런타임 종속성 구성과 버전 정합성을 확인하세요. 그렇지 않다면 최초 원인에 표시된 작업을 수정하세요. 로그에 뒤따른 오류만 남아 있다면 시작 로그를 켜거나 새 JVM을 시작하는 집중 테스트로 문제를 재현하세요. 실패한 클래스는 기존 클래스 로더 안에서 안전하게 초기화 상태를 되돌릴 수 없습니다.

초기화 코드 수정 및 애플리케이션 검증

실패한 입력이나 작업을 수정하세요. 예를 들어 속성 파일을 안정적으로 설정된 위치에 두고, 우연한 작업 디렉터리에 의존하지 않도록 명시적인 경로를 사용하세요. 시작 시 필수 값을 검증하고 리소스나 설정 항목의 이름을 포함한 명확한 오류를 보고하세요. 값이 런타임 설정에 따라 달라진다면 호출자에게 유용한 오류를 반환할 수 있는 명시적 초기화 메서드를 사용하는 편이 좋습니다.

초기화 예외를 잡은 뒤 null이나 불완전한 값으로 계속 실행하지 마세요. 그렇게 하면 애플리케이션을 안전하지 않은 상태로 둔 채 최초 원인을 숨기게 됩니다. 애플리케이션에 대체 값이 유효하다면 이를 명확히 정의하고 검증하세요. 그렇지 않다면 원인 예외를 포함해 시작을 실패 처리하세요.

원인을 수정한 뒤 새 프로세스를 실행해 해당 클래스가 초기화되는지, 그리고 그 클래스를 사용하는 애플리케이션 작업이 성공하는지 모두 확인하세요. 성공적인 정적 초기화의 간단한 예는 다음과 같습니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

public class InitializationCheck {
  static class Settings {
    static final String MODE = "ready";
  }

  public static void main(String[] args) {
    System.out.println(Settings.MODE);
  }
}

출력은 다음과 같습니다.

ready

그런 다음 문제가 발생했던 환경에서도 같은 서비스 계정, 작업 디렉터리, 설정, 런타임 종속성을 사용해 확인하세요. 재시작만으로 증상이 일시적으로 사라질 수 있습니다. 신뢰할 수 있는 검증은 의도한 설정에서 최초에 실패했던 작업이 이제 성공하는지 확인하는 것입니다.