내가 만든 자바 코드가 어떤 과정으로 실행되는지 알아보기 위한 "JVM 알아보기" 두 번째 포스팅입니다.
이번 글은 JVM 의 구성 요소 중 하나인 ClassLoader 에 대해서 알아보겠습니다.
JVM Architecture
JVM 의 구성요소는 크게 세 가지다.
- Class Loader Subsystem
- Runtime Data Areas
- Execution Engine
이번 포스팅에서는 먼저 Class Loader 부터 알아보도록 하자.
ClassLoader의 동작 과정
Java 클래스는 한 번에 전부 메모리에 로드되지 않고 사용 시점에 로드된다.
이 때 클래스 로더가 JRE 에 의해 호출되고, 동적으로 클래스를 메모리에 로드한다.
클래스 로더는 주로 Loading, Linking, Initialization 세 가지 역할을 수행한다.
Loading
컴파일 된 ".class" 파일은 플랫폼과 기계에 독립적인 byte code 상태이다.
로딩 단계에서 클래스 로더는 ".class" 파일에서 해당 바이너리 데이터를 생성하여 메서드 영역에 저장한다.
각 ".class" 파일에 대해 JVM 은 메서드 영역에 다음 정보를 저장한다.
- 로드된 클래스 및 직계 부모 클래스의 정규화된 이름
- ".class" 파일이 Class, Interface, Enum 과 관련되어 있는지 여부
- 수정자, 변수 및 메서드 정보 등
".class" 파일을 로드한 후에 JVM 은 Class 타입의 객체를 생성한다.
객체는 메모리의 heap 이라는 영역에 저장이 되는데, 이렇게 객체를 생성하면 메모리 내에서 ".class" 파일을 나타낼 수 있다.
이 객체는 java.lang 패키지에 미리 정의되어 있는 타입으로, 클래스 이름, 메서드 및 변수 정보 등과 같은 값을 가져올 수 있다.
이 객체의 참조를 얻으려면 Object 클래스의 getClass() 메서드를 사용하면 된다.
다음과 같이 클래스 수준의 정보를 가져올 수 있다.
Java
package c.jvm;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) {
Student s1 = new Student();
Class c1 = s1.getClass();
System.out.println(c1.getName());
Method m[] = c1.getDeclaredMethods();
for (Method method : m)
System.out.println(method.getName());
Field f[] = c1.getDeclaredFields();
for (Field field : f)
System.out.println(field.getName());
}
}
class Student {
private String name;
private int number;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getNumber() { return number; }
public void setNumber(int number)
{
this.number = number;
}
}
Output
c.jvm.Student
getName
setName
getNumber
setNumber
name
number
클래스 로더에는 일반적으로 세 가지 유형이 있다.
- Bootstrap ClassLoader
다른 모든 ClassLoader 의 부모로, C, C++ 과 같은 네이티브 언어로 구현된다.
Java 객체가 아니기 때문에 다음과 같이 ClassLoader 를 가져오려고 하면 null 을 리턴한다.
Java
package c.jvm;
public class Test {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
System.out.println(Test.class.getClassLoader());
}
}
Output
null
jdk.internal.loader.ClassLoaders$AppClassLoader@251a69d7
JVM 을 동작시키기 위한 가장 핵심적인 Java 클래스(java.lang.* 등)를 로드한다.
java 명령어로 애플리케이션을 실행하면, JVM 위에서 애플리케이션이 동작한다.
다음과 같은 옵션을 주면 클래스를 로딩할 때 로딩하는 라이브러리들을 확인할 수 있다.
java -verbose:class [Class Name]
샘플 클래스를 실행해보면 수많은 클래스들이 로드되는 것을 확인할 수 있다.
- Platform ClassLoader(Java9+)
Bootstrap ClassLoader 의 자식으로, JDK 확장 디렉토리(일반적으로 JRE의 lib/ext)에서 클래스들을 로드한다.
- System ClassLoader
Platform ClassLoader 의 자식으로, 클래스패스나 모듈패스에 있는 클래스를 로드한다.
왜 3개의 클래스 로더가 필요할까? 그 이유는 세 가지 수준의 신뢰를 나타내기 때문이다.
가장 신뢰할 수 있는 클래스는 핵심 API 클래스다. 다음은 설치된 확장과 클래스 패스의 클래스(로컬)다.
(참고) What is the use of Custom Class Loader
Linking
verification, preparation 그리고 선택적으로 resolution 을 수행한다.
- verification
파일이 올바른 형식으로 되어있고, 유효한 컴파일러에 의해 생성되었는지 여부를 검증한다.
검증에 실패하면 런타임 예외 java.lang.VerifyError 가 발생한다.
ByteCodeVerifier 에 의해 수행되며, 검증이 완료되면 클래스 파일을 컴파일할 준비가 된 것이다.
비용이 매우 크기 때문에, 옵션을 통해서 Disabled 할 수 있다.
-preparation
클래스 변수에 대해 메모리를 할당하고 기본값으로 초기화한다.
기본값은 다음과 같다.
- resolution(optional)
symbolic references를 메서드 영역에 있는 direct references 로 교체한다.
Initialization
이 단계에서 모든 static 변수는 정의된 값으로 할당된다.
ClassLoader 의 상속 관계
ClassLoader 의 특징
Java ClassLoader 에는 세 가지의 특징이 있다.
Delegation Model 위임 모델
클래스 로더는 Delegation Hierachy Algorithm 을 사용하여 Java 파일에 클래스를 로드한다.
Delegation Hierachy Algorithm
System ClassLoader-> Platform ClassLoader-> Bootstrap ClassLoader 순서로 작동한다.
Bootstrap ClassLoader는 항상 더 높은 우선순위를 가지며, 그 다음은 Platform ClassLoader, 그 다음은 System ClassLoader 다.
- 클래스 로딩을 요청받은 System ClassLoader 는 클래스 로딩을 직접 하지 않고 부모 클래스로더에게 위임한다.
- 요청을 위임받은 Platform ClassLoader 도 마찬가지로 Bootstrap ClassLoader 에게 위임한다.
- Bootstrap ClassLoader 는 앞에서 설명한 것과 같이 가장 핵심적인 Java 클래스를 로드하는 클래스 로더다. 찾고 있는 클래스가 핵심 클래스에 없다면 Platform ClassLoader, System ClassLoader 순으로 클래스를 찾는다.
- 만약 존재하지 않는다면 ClassNotFoundException 이 발생한다.
Visibility Principle 가시성 원칙
부모 ClassLoader에 의해 로드된 클래스는 자식 ClassLoader에서 확인할 수 있지만
자식 ClassLoader에 의해 로드된 클래스는 부모 ClassLoader에서 확인할 수 없다.
Test.class 가 Platform ClassLoader 에 의해 로드되었다면
Platform ClassLoader 나 System ClassLoader 에서는 확인할 수 있지만, Bootstrap ClassLoader 에서는 확인할 수 없다.
해당 클래스가 Bootstrap ClassLoader 를 사용하여 다시 로드하려고 하면 java.lang.ClassNotFoundException 예외가 발생한다.
사용자가 java.lang.MyClass 라는 클래스를 만들었다고 가정하자.
이론적으로는 같은 패키지의 모든 필드와 메서드에 대한 패키지 액세스 권한을 얻고 java.lang 의 작동 방식을 변경할 수 있다.
하지만, java.lang 패키지의 클래스는 부트스트랩 클래스 로더에 의해 로드 되었기 때문에 가시성 원칙에 의해 JVM이 이를 차단하게 된다.
동일한 로더가 아님 = 액세스 권한 없음
Uniqueness Property 유일성
자식 클래스 로더는 부모 클래스 로더가 로딩한 클래스를 다시 로딩하지 않는 것을 보장한다.
마치며
작성한 자바 코드를 클래스 파일로 만든 후 이를 ClassLoader 를 통해 로드하는 방식 및 특징을 알아보았습니다.
다음 포스팅에서는 ClassLoader 가 로드한 데이터를 저장하는 JVM 의 메모리구조에 대해 알아보도록 하겠습니다.
Reference
- GeeksforGeeks How JVM Works - JVM Architecture?
- Java 클래스로더 흝어보기
- JVM에 관하여 - Part 2, ClassLoader
- JVM. 클래스로더 서브시스템(Class Loader Subsystem)
'Java' 카테고리의 다른 글
[Java] JVM 알아보기 - (4) JVM GC, Heap (0) | 2022.11.27 |
---|---|
[Java] JVM 알아보기 - (3) JVM Memory Structure (0) | 2022.11.23 |
[Java] JVM 알아보기 - (1) JDK, JRE, JVM (0) | 2022.11.16 |
[Java] Generic 과 WildCard (0) | 2022.08.31 |
[Java] Wrapper class 와 일급 컬렉션 (1) | 2022.08.25 |