클래스로더
JVM은 자바 바이트 코드 파일(.class)을 읽어서 실행하는데, 이 class 파일을 동적으로 런타임 데이터 영역에 로딩해주는 것이 클래스로더이다.
HelloWorld.java 파일에 HelloWorld 출력소스가 있다고 가정해보자. 이 자바소스를 실행하면 OS는 가상 머신 프로세스(자바 바이너리)를 구동한다. 자바 가상 환경이 구성되고 스택 머신이 초기화된 다음 HelloWorld 클래스 파일이 실행된다. 알다시피 애플리케이션의 진입점은 HelloWorld.class에 있는 main() 메서드이다. 제어권을 이 클래스로 넘기려면 이 클래스를 로드해야 한다.
자바 프로세스가 새로 초기화되면 사슬처럼 줄지어 연결된 클래스로더가 차례차례 작동한다.
클래스로더는 3가지 단계로 작동하는데 로딩(Loading), 링킹(Linking), 초기화(Initializing) 단계로 작동한다.
1. 로딩(Loading)
HelloWorld를 출력하는 main()메소드를 실행하기위해 제어권을 이 클래스로 넘기려면 이 클래스를 로드해야 한다. 이 로드에도 순서가 있다.

1-1) 부트스트랩 클래스로더(Bootstrap classloader)
부트스크랩 클래스로더는 JVM기동 시 가장 최초로 실행되며, 다른 클래스로더가 나머지 시스템에 필요한 클래스를 로드할 수 있게 최소한의 필수 클래스(java.lang.Object, Class, Classloader)만 로드한다. 다른 클래스로더와 달리 자바가 아닌 네이티브 코드로 구현되어 있다.
1-2) 확장 클래스로더(Extension classloader)
부트스트랩 클래스로더를 자기 부모로 설정하고 필요할 때 클래스로딩 작업을 부모에게 넘긴다. 기본 자바 API를 제외한 확장 자바 클래스들을 로드하는데, 다양한 보안 확장 기능 등을 여기서 로드하게 된다.
1-3) 애플리케이션 클래스로더(Application classloader)
부트스트랩 클래스로더와 확장 클래스로더가 JVM 자체 구성 요소들을 로드하는 것이라면, 애플리케이션 클래스로더는 지정된 클래스패스에 위치한 유저 클래스 혹은 Jar에 속한 클래스들를 로드한다.
만약, 내가 사용하는 클래스들이 어느 시점에 로딩되는지 궁금하다면, .class.getClassloader()로 확인할 수 있다.

rt.jar는 최소한의 자바클래스이기에 부트스트랩 클래스로더가 로드한다.
java.lang, java.util 등 필수패키지는 rt.jar에 있기때문에 전부 부트스트랩 클래스로더에 의해 로드된다. (부트스트랩 클래스로더는 네이티브코드로 작성되어 null로 표현되는 것 같다.)
모든 클래스들의 최상위 부모인 Object 클래스도 java.lang 패키지에 속하기 때문에, 부트스트랩 클래스로더에 의해 로드된다.
그렇다면, JDK는 이 클래스파일들을 어떻게 찾는걸까?
Classpath에 지정한 클래스파일과 jar에 속한 클래스들을 로드하는데, 클래스패스는 말 그대로 클래스를 찾기위한 경로이다.
클래스패스는 JVM이 프로그램을 실행할 때, 클래스파일을 찾는 데 기준이 되는 파일 경로인데, .java로 끝나는 소스파일을 컴파일하면 "바이트코드"(바이너리 형태의 .class파일)로 변환된다. JVM이 바이트코드로 된 파일을 찾아야 실행할 수 있는데 이 바이트코드까지의 경로를 클래스패스라고 한다.
java runtime은 이 classpath에 지정된 경로를 모두 탐색하여 특정 코드가 포함된 .class파일을 찾는다.
보통 이 classpath를 설정하는 방법은 2가지가 있는데 대부분 환경변수 CLASSPATH 설정에서 할 것이다.
또 하나는, java runtime에 -classpath 플래그를 사용하는 방법이다.
(여기서 환경변수를 JDK 환경변수설정으로 오해할 수 있는데, 프로젝트의 환경변수를 말한다.)
2. 링킹(Linking)
Linking은 로드된 클래스 파일들을 검증하고, 사용할 수 있게 준비하는 과정을 의미한다.
Linking은 Verification, Preparation 그리고 Resolution 세 단계로 이루어져 있다.
2-1) Verification(검증)
클래스 파일이 유효한지를 확인하는 과정이다.
자바 언어 명세 및 JVM명세대로 구성되 있는지 검사하고, 실패하면 java.lang.VerifyError를 발생시킨다.
검증된 컴파일러가 만든 class파일이 아닌, 제 3자가 수동으로 변경한 경우에도 검증을 실패하기에 악의적인 변경을 방지할 수 있다.
2-2) Preparation(준비)
클래스에 필요한 메모리를 할당하고 클래스의 필드, 메서드, 인터페이스를 나타내는 데이터 구조를 준비한다.
메모리를 할당할 때 static field값들은 초기화(Initialization)과정에서 코드에 작성한 초기값으로 초기화된다.
2-3) Resolution(분석)
클래스의 상수 풀(Constant pool) 내 모든 Symbolic Reference값을 JVM의 메모리 구성 요소인 Method Area의 런타임 환경 풀을 통하여 실제 메모리에 직접 참조(Direct Reference)하도록 교체한다.
다시 말해, 추상적인 기호를 구체적인 값으로 동적으로 교체하는 과정이다.
*심볼릭 레퍼런스 : 메모리 번지가 아닌 이름에 의한 참조 (간단 정리 필요)
3. 초기화(Initialization)
링킹 단계에서 확보한 메모리 영역에 클래스의 static변수를 명시된 값으로 할당한다.
JVM은 멀티 쓰레딩으로 작동을 하며, 같은 시간에 한 번에 초기화를 하는 경우가 있기 때문에 동시성을 고려해야 한다.
'덕개' 카테고리의 다른 글
| [Java - Basic] 인터페이스, default, 다형성 (0) | 2024.04.27 |
|---|---|
| [Vue.js] Vuex를 대체할 Pinia에 대해 알아보자. (0) | 2023.04.14 |
| [Javascript] TypeScript 1 - 타입스크립트란? (자바스크립트를 타입스크립트처럼 써보자) (0) | 2023.02.12 |
| [Vue.js] 엑셀(Excel) 다운로드 기능 구현하기 ExcelJS, file-saver (0) | 2023.02.10 |
| [Javascript] var , let , const 차이 (ES5, ES6) (0) | 2023.01.19 |