클래스 충돌, 왜 발생할까요?
Java 웹 애플리케이션을 개발하다 보면
"이 클래스 왜 안 불러와지지?", "같은 이름인데 충돌이 나네?"
이런 현상을 자주 겪게 됩니다.
특히 WAS(Web Application Server)를 사용하는 경우, Java SE와는 다른 클래스 로딩 구조를 가지고 있어서
충돌이나 예외가 발생하는 원인을 이해하지 못하면 디버깅에 큰 어려움을 겪습니다.
이 글에서는 다음 내용을 중심으로 개념을 정리해 봅니다.
- Java 클래스 로딩 순서와 부모 위임 모델
- 시스템 클래스 오버라이드 가능 여부
- WAS에서 클래스 우선순위가 달라지는 이유
- 같은 클래스가 classes, JAR, WAS 모듈에 있을 때 어떤 게 로딩될까?
Java의 기본 클래스 로딩 구조
Java는 기본적으로 부모 위임 모델 (Parent Delegation Model) 을 사용합니다.
즉, 클래스 로더가 클래스 로딩 요청을 받으면 자기 자신이 처리하기 전에 부모에게 먼저 위임합니다.
클래스 로딩 위임 순서
- Bootstrap ClassLoader (JDK의 핵심 클래스, java.lang 등)
- Platform ClassLoader (Java 9 이전에는 Extension ClassLoader)
- Application ClassLoader (classpath, WEB-INF/classes, lib/*.jar 등)
부모가 클래스를 로딩할 수 있으면 자식은 로딩하지 않고, 이미 로드된 클래스를 그대로 사용합니다.
시스템 클래스는 오버라이드할 수 없다
예를 들어 내가 직접 만든 java/util/ArrayList.class 파일을 넣었다 하더라도,
JVM은 해당 클래스를 무조건 Bootstrap ClassLoader에서 로딩합니다.
사용자 정의 클래스는 무시되며,
잘못하면 SecurityException, LinkageError 같은 예외가 발생합니다.
이는 JVM 보안 정책 때문이며,
java.* 또는 javax.* 계열의 클래스는 절대로 오버라이드할 수 없습니다.
WAS에서는 클래스 로딩 방식이 다르다
대부분의 WAS(Web Application Server)는 Java SE와는 다르게
클래스 로더 격리 정책(ClassLoader Isolation)을 사용합니다.
즉, 부모 위임보다는 애플리케이션 자체의 classes나 lib JAR 파일을 먼저 로딩합니다.
이런 정책은 애플리케이션 간의 충돌을 방지하고, 서버 안정성을 높이기 위해 사용됩니다.
WAS 클래스 로딩 우선순위
다음은 일반적인 WAS에서의 클래스 로딩 우선순위입니다:
- WEB-INF/classes (애플리케이션 클래스)
- WEB-INF/lib/*.jar (애플리케이션 라이브러리)
- 서버 공통 모듈 또는 공유 클래스 (예: WAS 내장 모듈)
- JDK 시스템 클래스 (java.*)
즉, 같은 클래스가 서버 모듈과 애플리케이션 양쪽에 동시에 존재할 경우,
WAS는 보통 애플리케이션 쪽 클래스를 먼저 로딩합니다.
충돌이 발생하는 대표적인 사례
- ClassCastException: 같은 클래스인데 로더가 달라서 타입 호환이 안 됨
- LinkageError: 패키지 이름 중복, 클래스 정의 충돌 등
- SecurityException: 시스템 클래스를 잘못 오버라이드하려고 시도했을 때
클래스 로딩 흐름도
클래스 로딩이 실제로 어떤 경로를 따라 진행되는지를 시각적으로 이해하고 싶다면
아래의 흐름도를 참고하세요.
항목 Java SE WAS 환경
로딩 모델 | 부모 위임 | 애플리케이션 우선 (격리 정책) |
시스템 클래스 오버라이드 | 불가 | 불가 |
사용자 클래스 오버라이드 | 가능 | 가능 (보통 classes가 우선) |
충돌 가능성 | 낮음 | 중복 시 높음 (주의 필요) |
WAS에서 시스템 모듈 클래스가 먼저 로딩되는 이유
지금까지는 일반적인 로딩 우선순위를 다뤘지만, 실제 WAS 환경에서는 서버 모듈 클래스가 먼저 로딩되어 lib 클래스가 무시되는 경우가 존재합니다.
예를 들어 WAS에는 다음과 같은 시스템 모듈이 존재합니다:
- org.jboss.logging.Logger → WildFly 로깅 시스템
- javax.transaction.UserTransaction → 서버 트랜잭션 모듈 등
이런 클래스들은 WAS 부팅 시점에 시스템 모듈로 먼저 메모리에 로딩됩니다.
이후 애플리케이션이 동일한 FQCN의 클래스를 lib 경로에 포함하더라도,
JVM은 이미 로딩된 클래스를 우선 사용하기 때문에 무시됩니다.
왜 이런 일이 발생할까?
JVM은 같은 이름의 클래스(FQCN)가 이미 로딩되었으면,
하위 로더에 있더라도 다시 로딩하지 않습니다.
그리고 WAS는 시스템 모듈을 **상위 클래스 로더(ModuleClassLoader)**에서 먼저 로딩하기 때문에,
애플리케이션 레벨(lib/*.jar)은 상위에 의해 이미 로딩된 클래스를 덮어쓸 수 없습니다.
실제로 발생하는 문제들
- slf4j, logback 등 로깅 설정이 무시되고, WAS 기본 로깅(jboss-logging)만 동작
- 기대한 log4j 버전이 아닌 시스템 모듈 버전이 사용됨
- 인터페이스 충돌로 인해 ClassCastException 발생
어떻게 대응하면 좋을까?
- 같은 FQCN(전체 클래스 이름)을 가진 클래스가 여러 위치에 존재하지 않도록 관리하세요.
- 서버 공용 모듈과 충돌 가능성이 있는 경우, WAS 설정 파일(deployment-structure.xml 등)을 통해 모듈 제외 처리를 고려하세요.
- 로딩 순서나 위치를 정확히 확인하고 싶다면 -verbose:class 옵션이나 getClass().getClassLoader() 메서드를 활용하세요.
마치며
클래스 로더는 이해하기 어렵지만, 한 번 구조를 파악하고 나면
WAS 환경에서 발생하는 많은 충돌 문제를 쉽게 해결할 수 있습니다.
이 글이 클래스 로딩 우선순위를 이해하는 데 도움이 되었기를 바랍니다.
'프로그래밍 언어 > JAVA' 카테고리의 다른 글
Java 직렬화와 NotSerializableException 문제 (0) | 2025.01.02 |
---|---|
Java로 TCP 프록시 서버 구현하기 - 라운드 로빈 로드밸런싱을 활용한 백엔드 서버 연결 (1) | 2025.01.02 |