카테고리 없음

당신이 작성한 자바 코드가 실행되기 까지의 과정들 - 클래스 로더 편

martial 2022. 12. 22. 15:57

안녕하세요. 이 글을 쓰게 된 계기는 특정 분야에 국한되지 않고 JVM 생태계 언어를 사용중이신 분들에게 도움이 됐으면 해서 작성합니다.  제 글에 문제점이 있거나 수정해야 될 부분이 있다면 댓글로 피드백 부탁드립니다.

 

아래는 JVM의 구조를 나타낸 그림이에요 중간중간 이해가 안되면 그림을 보고 flow를 이해보시는것도 좋을거 같아요.

 

우선 예제를 위해 간단한 자바 파일을 하나 만들고 아래와 같이 작성해 줄게요.

 

public class App {
    public static void main(String[] args) {

    }
}

 

이제 JVM을 통해서 이 자바파일을 한번 실행시키는 과정을 봐볼게요

 

  • 첫번째, 클래스 로더가 제가 작성한 App.class를 로딩합니다.

 

 

로딩이란 과정은 클래스 로더가 .class 파일을 읽고 이 파일에 대한 바이너리 데이터를 만들고 JVM의 메모리 영역중 메소드영역에 저장함을 의미합니다. 이 메소드 영역에 저장하는 내용은 FQCN(Fully Qualified Class Name, 클래스명이 포함된 Full Package Name)과 어떤 Reference Type 인지 (Class, Interface, Enum) 와 해당 .class 파일의 메소드와 변수를 저장합니다.

 

로딩 과정이 끝나면 해당 클래스타입의 클래스 객체를 JVM 메모리 영역중 힙 영역에 저장합니다. ex) Class<App>

 

여기서 로딩을 해주는 로더의 종류는 3가지가 존재합니다.

Bootstrap Class Loader, Extenstions Class Loader(Platfrom Class Loader), Application Class Loader

 

로더들은 계층형 구조를 가지고 있어요 즉 부모 자식 관계가 존재한다는 말이에요.

실제 제 loacl project에서 App.class를 로딩하는 로더를 로그로 확인해볼게요.

public class App {
    public static void main(String[] args) {
        ClassLoader classLoader = App.class.getClassLoader();
        System.out.println("classLoader = " + classLoader);
        System.out.println("classLoader.getParent() = " + classLoader.getParent());
        System.out.println("classLoader.getParent().getParent() = " + classLoader.getParent().getParent());
    }
}

실행을 시키면..

 

 

해당 로그가 찍히는데요 로그를 보시면 현재 App.class를 로딩한 로더는 Application Class Loader이고
Application Class Loader의 부모는 Platform Class Loader 이고 또 그의 부모는 null이라고 로그가 작성 됐네요.

 

제 글을 집중해서 보셨더라면 이상한점을 하나 발견 하셨을 텐데 바로 Platform Class Loader의 부모는 분명 Bootstrap Class Loader 인데 왜 null이지? 라고 생각하실 수 있어요. 그 이유는 바로 Bootstrap Class Loader가 네이티브 코드로 작성되었기 때문이에요.

 

여기서 네이티브 코드란 C, C++, 어셈블리어 같은 다른 언어로 이루어져 있는 코드를 자바와 함께 사용하는 코드를 뜻하는데 이걸 가능케 하는것이 바로 JNI 입니다. 블로그 맨 위의 사진 Execution Engine(실행 엔진) 옆에를 보면 확인하실 수 있는데 이것은 다음에 설명드리도록 하겠습니다.

 

자 본론으로 돌아와 App.class를 로딩한 로더는 Application Class Loader 인것을 확인해는데 각 로더들은 어떤 자바 파일들을 로딩 할까요?

 

  • 부트 스트랩 클래스 로더 - JAVA_HOME\lib에 있는 코어 자바 API를 제공한다. 최상위 우선순위를 가진 클래스 로더

 

  • 플랫폼 클래스로더 - JAVA_HOME\lib\ext 폴더 또는 java.ext.dirs 시스템 변수에 해당하는 위치에 있는 클래스를 읽는다.

 

  • 애플리케이션 클래스로더 - 애플리케이션 클래스패스(애플리케이션 실행할 때 주는 -classpath 옵션 또는 java.class.path 환경 변수의 값에 해당하는 위치)에서 클래스를 읽는다.

 

다음과 같이 로딩을 하는데 대부분 우리가 작성하는 자바 파일들은 대부분 Application Class Loader가 로딩한다고 보면 될것 같습니다.

또 로더는 부모 자식관계가 존재한다고 말했는데 여기서 클래스를 로딩할때 제일 상위의 부모 로더가 읽고 부모 로더가 읽지 못한다면 자식 로더가 읽게 됩니다. (Bootstrap Loader -> Platform Loader -> Application Loader 순) 여기서 모든 로더가 클래스 로딩에 실패한다면 ClassNotFoundException이 발생하게 됩니다.  

 

 

 

 

  • 두번째, 로딩된 클래스를 링크 한다.

 

링크는 총 3가지 과정 Verify, Prepare, Resolve 으로 이루어져 있습니다

 

Verify 단계는 로딩된 클래스가 .class 파일 형식인지 검증합니다.

Prepare 단계는 static 변수와 기본값에 필요한 메모리를 준비하는 과정입니다.

Resolve 단계는 심볼릭 메모리 레퍼런스를 Heap 영역에 존재하는 실제 레퍼런스로 교체하는 과정입니다. 

 

만약 App app = new App(); 이런식으로 레퍼런스를 할때

App에 대한 레퍼런스는 심볼릭 메모리 레퍼런스 입니다. 이말은 즉슨 실제 레퍼런스를 가르키고 있지 않다는 말입니다.

 

즉, 링크 과정에서 연결된 레퍼런스들은 심볼릭 메모리 레퍼런스로 존재합니다.

 

하지만 Resolve 단계는 Optional로 Resolve 단계에서 실제 레퍼런스가 연결될 수도 있고 객체의 사용시점에 레퍼런스가 연결될 수도 있습니다.

 

 

 

  • 마지막, 링크까지 통과된 클래스들을 초기화 한다.

 

이 초기화 단계는 static한 영역들을 모두 할당해주는 단계입니다.

 

static String name = "martial";
static String name;

static {
	name = "martial"
}

이와 같은 코드들이 모두 초기화 단계에 할당됩니다.

 

 

이상, 클래스 로더 편이었습니다. 다음 포스팅에는 클래스 로더를 거친 클래스가 어떻게 실행되는지 알아보도록 하겠습니다. 긴 글 읽느라 수고 많으셨습니다.