전자정부프레임워크 3.5.1 버전을 사용하면서 openCV를 jar파일을 라이브러리에 직접 추가하고, 외부 라이브러리 파일 .dll파일을 -Djava.library.path로 직접 경로를 추가해줬을 때, System.loadLibrary(Core.NATIVE_LIBRARY_NAME)로 OpenCV를 사용하기 위한 라이브러리 로드를 실행하니 첫 실행은 문제가 없었지만 코드 수정 후 재빌드 이후부터 클래스로더에 이미 존재한다는 에러가 뜨는 문제가 있었습니다.
이 문제를 해결하기 위해 코드를 직접 수정하면 무조건 서버를 다시 껐다가 켜야하는 문제가 있었는데 이 때 알게 된 클래스로더에 대해서 한 번 탐구해보고 무슨 문제인지 명확히 하기 위해 이 글을 작성하게 되었습니다.
하면서 알게 된 것인데 후에 네이티브 로더 라는 것도 추가해서 넣어보도록 할게요.
JVM의 클래스 로더
- Java Virtual Machine에서 클래스 파일을 메모리에 로드하고 실행 가능한 상태로 만드는 역할을 하는 모듈
- 일반적으로 Java는 동적 로드(클래스를 런타임에 링크하고 로드하는 특징이 있음)
- 즉, 클래스로더란 JVM이 미리 모든 클래스에 대한 정보를 메소드 영역에 로딩하지 않고 런타임 시점에 동적으로 로드해줄 수 있게 해주는 것
- ClassLoader 함수 문서 : https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/ClassLoader.html
런타임 내장 클래스 로더 (JDK-17 기준)
- Bootstrap ClassLoader
- JVM의 내장 클래스 로더
- 일반적으로 null로 표현되며 부모가 없음 (최상위 클래스 로더)
- JVM 시작 시 가장 최초로 실행되는 클래스 로더
- Java SE API의 기본 클래스를 로드
- JAVA_HOME/lib/modules에 있는 java.base 모듈을 로드
- (java.lang.Object, java.lang.String, java.lang.Class ... 등)
- JVM의 내장 클래스 로더
- Platform ClassLoader (과거 Extension ClassLoader)
- Bootstrap ClassLoader를 부모로 갖는 클래스 로더 (JVM의 기본 클래스 로더)
- JRE 내부의 기본 라이브러리를 참조하므로, 자바프로그램에서 기본적으로 제공되는 라이브러리들을 로드
- 추가로 java.ext.dirs 환경 변수에 설정된 디렉토리의 클래스 파일을 로드하고, 이 값이 설정되어 있지 않은 경우 ${JAVA_HOME}/jre/lib/ext에 있는 클래스 파일을 로드
- Java SE API 및 해당 구현 클래스를 로드
- JAVA_HOME/lib 폴더나 jmods 폴더에 있는 모듈들을 로드
- (java.*의 클래스 들 ex. java.io, java.net, java.lang, java.util ...)
- Bootstrap ClassLoader를 부모로 갖는 클래스 로더 (JVM의 기본 클래스 로더)
- System ClassLoader (Application ClassLoader)
- Platform ClassLoader를 부모로 갖는 클래스 로더
- 애플리케이션 코드에서 사용하는 Jar파일이나 클래스 파일들을 로드
- 사용자가 만든 애플리케이션의 클래스를 로드하며, 애플리케이션의 클래스패스에서 클래스를 찾음
- Platform ClassLoader를 부모로 갖는 클래스 로더
클래스로더 동작 방식
- JVM의 메소드 영역에 클래스가 로드되어 있는지 확인 (만약 로드되어 있다면 해당 클래스 사용)
- 메소드 영역에 로드되어 있지 않을 경우, 시스템 클래스 로더에 클래스 로드를 요청
- 시스템 클래스 로더는 플랫폼 클래스 로더에 요청을 위임
- 플랫폼 클래스 로더는 부트스트랩 클래스 로더에 요청을 위임
- 부트스트랩 클래스로더는
부트스트랩Classpath(JDK/JRE/LIB)에 해당 클래스 있는지 확인 - 만일 클래스가 존재하지 않는 경우 다시 플랫폼 클래스 로더에게 요청 넘김
- 플랫폼 클래스 로더는
플랫폼ClassPath(JDK/JRE/LIB/EXT)또는 java.ext.dirs 디렉토리에서 해당 클래스가 있는지 확인 - 만일 클래스가 존재하지 않는 경우 다시 시스템 클래스 로더에게 요청 넘김
- 시스템 클래스 로더에 클래스가 존재하지 않는 경우 ClassNotFoundException을 발생
스프링부트에서의 클래스 로더 동작 (Spring MVC는 SpringBoot ClassLoader만 생략)
- 기본적으로 JVM의 클래스로더 동작 방식이랑 비슷함
- 하지만 추가로 SpringBoot ClassLoader, TomcatWebApp ClassLoader, SpringBean ClassLoader가 추가로 존재
- SpringBoot ClassLoader
- 애플리케이션을 패키징된 JAR로 실행할 때 클래스를 로드하는 방식에 변화를 주며, 이 클래스를 통해 자체 실행 JAR파일 안에 포함된 리소스들을 효율적으로 관리
- SpringBoot JAR의 구조
- BOOT-INF/classes/ 디렉토리 아래에 애플리케이션의 클래스가 포함되고, BOOT-INF/lib/ 디렉토리에 의존성 JAR 파일들이 포함
- JAR 내의 클래스를
URLClassLoader나PathMatchingResourcePatternResolver를 이용해 로드- 이는 시스템 클래스 로더의 역할을 확장하여 Spring Boot 특유의 클래스 로딩 방식
- 이 로딩 방식은 Java 클래스 로딩 메커니즘에 의해 제공되는 기능 외 Spring의 기능을 추가적으로 처리
따라서 동작 방식은 클래스로더 동작방식에서 시스템 클래스 로더 실행 후 스프링부트 클래스 로더가 동작- SpringBean ClassLoader
- ApplicationContext에 등록된 Bean들을 로드하는 데 사용되는 클래스 로더
- Spring의 AOP나 Bean 생명주기 관리 등을 다루는 데 관련된 클래스를 로드함
- Spring에서 빈을 로드할 때 빈 클래스와 관련된 의존성 및 설정 파일들도 함께 로드됨
- Bean들을 IoC 컨테이너에 관리하기 위해 사용. 보통 Bean들은 Java 클래스로 정의되고 스프링 컨텍스트에서 관리됨
- ApplicationContext에 등록된 Bean들을 로드하는 데 사용되는 클래스 로더
- TomcatWebApp ClassLoader
- Tomcat과 같은 Servlet Container에서 사용되는 클래스 로더
- Tomcat은 웹 애플리케이션을 실행할 때 WAR 파일을 로드하는 데 사용함
- WEB-INF/classes, WEB-INF/lib 디렉토리 아래에 클래스를 로더
- Tomcat과 같은 Servlet Container에서 사용되는 클래스 로더
- Spring Boot와 Spring MVC 애플리케이션 비교
- Spring Boot
- 내장 서버(Tomcat, Jetty 등)을 사용하므로 TomcatWebApp ClassLoader가 기본적으로 내장된 서블릿 컨테이너에서 애플리케이션을 실행할 때 사용
- Spring Framework
- 웹 애플리케이션을 서블릿 컨테이너에서 실행할 때 사용 (TomcatWebApp ClassLoader)
- 결론적으로 국밥을 먹을 때 말아먹는다거나 아니면 따로 먹는다거나의 차이일 뿐
- Spring Boot
전체적인 흐름
Bootstrap ClassLoader
│
├── Platform ClassLoader
│ │
│ ├── System ClassLoader (애플리케이션 클래스 로드)
│ │
│ ├── SpringBoot ClassLoader (fat JAR 내부 클래스 로드)
│ │
│ ├── TomcatWebAppClassLoader (내장 Tomcat이 WAR 또는 Servlet 클래스 로드)
│ │ │
│ │ ├── SpringBean ClassLoader (Spring IoC Container에서 Bean 관리)
- SpringFramework는 시스템 클래스로더에 없으면 바로 TomcatWebAppClassLoader로 클래스를 확인함
결론
내가 가장 궁금한 것은 왜 OpenCV를 JVM에서 사용할 때 서버를 껐다가 켜야하는가에 대한 문제였고
전체적으로 외장 라이브러리의 실행 클래스로더의 흐름을 이해하게 되었다.
그런데 가장 중요한 게
.dll, .so, .dylib 같은 외부 라이브러리는 Native Loader라는 것으로 로더를 한다는 것이다.
로드 방식 (Windows 기반 System.loadLibrary())를 통해 개발자가 직접 실행시켜야 한다.
기존 ClassLoader가 메소드 영역에 저장되는 것과 달리 OS의 메모리 영역에 저장되는데
OpenCV를 사용하기 위해선 .dll파일을 적용시켜야 하므로 현재로써는 방법이 없다.
코드 수정으로 자동 재빌드 될 때 Native library가 새롭게 재빌드 되며 계속 클래스 파일이 바뀌는 문제로
.dll 파일을 계속 올려줘야 하는데, 가비지 컬렉션이 기존 클래스 파일을 빠르게 제거하지 못하면 재빌드로는 불가능하고
서버를 껐다가 다시 키는 방식으로 개발을 해줘야 하는 단점이 있다.
| 항목 | 클래스 로더 (ClassLoader) | 네이티브 로더 (NativeLoader) |
|---|---|---|
| 역할 | .class 파일을 메모리에 로드 |
.dll, .so, .dylib 같은 네이티브 라이브러리를 로드 |
| 로드 방식 | ClassLoader.loadClass() 사용 |
System.loadLibrary() 사용 |
| 저장 장소 | JVM의 메서드 영역 (Method Area) | OS의 네이티브 메모리 영역 |
| 관리 주체 | JVM의 ClassLoader 계층 구조 | OS의 네이티브 라이브러리 로더 (dlopen, LoadLibrary 등) |
| 예시 | Java 표준 라이브러리 (.jar) |
opencv_java470.dll (OpenCV 네이티브 라이브러리) |
'Spring' 카테고리의 다른 글
| [Spring Boot] 캐시 무효화(캐시 제어) - http응답의 캐시 제어 (6) | 2024.04.03 |
|---|---|
| [SpringFramework] -> [SpringBoot] 전환 (프로젝트로 실전 적용!) + JSP 사용하기 (4) | 2024.04.02 |
| [SpringFramework] 스프링 예외 처리 방법 (프로젝트로 실전 적용!) (2) | 2024.03.25 |