JVM의 전체적인 구조를 그려보면 해당 그림과 같다.
클래스 로더
클래스 로더는 바이트 코드(.class
)를 읽고 메모리의 적절한 위치에 저장한다. 클래스 로더의 구조를 좀 더 자세히 그려보면 다음과 같다.
클래스 로더는 크게 로딩, 링크, 초기화 순으로 진행이 된다. 각 과정들에 대하여 간단히 정리해보았다.
로딩
.class
파일을 읽고 적절한 바이너리 데이터를 만들고 메소드 영역에 저장을 한다.이때 메소드 영역에 저장하는 데이터는 다음과 같다.
- FQCN (Fully Qualified Class Name) : 패키지명까지 포함되어 있는 식별자이다.
- Class, Interface, Enum의 종류
- 해당 클래스의 메소드와 변수
이러한 로딩 과정이 끝나면 해당 클래스 타입의 Class 객체(
Class<App>
의 타입)를 생성하여 힙 영역에 저장하게 된다.또한, 로딩은 크게 Bootstrap, Extension, Application로 계층 구조를 이루고 있다.
부트스트랩 클래스 로더 : JAVA_HOME/lib에 있는 코어 자바 API를 제공하며, 최상위 우선순위를 가진 클래스 로더이다.
플랫폼 클래스 로더(Extension) : JAVA_HOME/lib/ext 폴더 또는 java.ext.dirs 시스템 변수에 해당하는 위치에 있는 클래스를 읽는 로더이다.
애플리케이션 클래스 로더 : 애플리케이션 클래스 패스 (애플리케이션 실행할 때 주는 -classpath 옵션 또는 java.class.path 환경 변수의 값에 해당하는 위치)에서 클래스를 읽는 로더이다.
위의 세 클래스 로더를 확인해보면 다음과 같다.
public class App { public static void main( String[] args ) { ClassLoader classLoader = App.class.getClassLoader(); System.out.println(classLoader); System.out.println(classLoader.getParent()); System.out.println(classLoader.getParent().getParent()); } }
jdk.internal.loader.ClassLoaders$AppClassLoader@512ddf17 jdk.internal.loader.ClassLoaders$PlatformClassLoader@3cb5cdba null
- 애플리케이션 클래스 로더의 부모가 플랫폼 클래스로더인 것을 확인할 수 있지만 플랫폼 클래스 로더의 부모는 null로 나온다. 이는 부트스트랩 클래스 로더가 네이티브로 짜여 있기때문이다.
static { // -Xbootclasspath/a or -javaagent with Boot-Class-Path attribute String append = VM.getSavedProperty("jdk.boot.class.path.append"); BOOT_LOADER = new BootClassLoader((append != null && append.length() > 0) ? new URLClassPath(append, true) : null); PLATFORM_LOADER = new PlatformClassLoader(BOOT_LOADER); // A class path is required when no initial module is specified. // In this case the class path defaults to "", meaning the current // working directory. When an initial module is specified, on the // contrary, we drop this historic interpretation of the empty // string and instead treat it as unspecified. String cp = System.getProperty("java.class.path"); if (cp == null || cp.length() == 0) { String initialModuleName = System.getProperty("jdk.module.main"); cp = (initialModuleName == null) ? "" : null; } URLClassPath ucp = new URLClassPath(cp, false); APP_LOADER = new AppClassLoader(PLATFORM_LOADER, ucp); }
- 따라서
ClassLoader
클래스 파일을 확인해보면 위의 코드와 같이BOOT_LOADER
,PLATFORM_LOADER
,APP_LOADER
를 확인해 볼 수 있다.
링크
- Verify :
.class
파일 형식이 유효한지를 체크한다. - Preparation : 클래스 변수(static 변수)와 기본값에 필요한 메모리를 준비한다.
- Resolve : 심볼릭 메모리 레퍼런스를 메소드 영역에 있는 실제 레퍼런스로 교체한다. (해당 과정은 Optional하다.)
- Verify :
초기화
- Static 변수의 값을 할당한다. (이때 static{} 블럭이 있다면 실행이 된다.)
메모리
메모리는 쓰레드들이 공유하는지에 대한 여부로 크게 두 분류로 나뉠 수 있다. 스택, PC 레지스터, 네이티브 메소드 스택은 각 쓰레드별로 하나씩 소유하게 되고, 힙과 메소드 영역의 경우 모든 쓰레드들이 공유하는 영역이다.
- 메소드 영역 : 클래스의 정보 (클래스 이름, 부모 클래스 이름, 메소드, 변수) 저장
- 힙 영역 : 동적할당이 되는 공간으로 생성되는 객체들이 저장된다.
- 스택 영역 : 각 쓰레드별로 런타임 스택을 만들고, 그 안에 메소드 호출을 스택 프레임이라 부르는 블럭으로 쌓는다. (에러 발생 시 쌓여있는 스택 프레임을 확인할 수 있다.)
- PC(Program Counter) 레지스터 : 쓰레드 별 현재 실행할 instruction의 위치를 가리키는 포인터를 가지고 있는다.
- 네이티브 메소드 스택 : 쓰레드가 네이티브 메소드를 호출할 때 쌓이게 되는 스택이다.
실행 엔진
- 인터프리터 : 바이트 코드를 한줄 씩 실행하는 역할을 수행
- JIT 컴파일러 : 인터프리터 효율을 높이기 위해 반복되는 코드를 발견하면 JIT 컴파일로로 해당 코드를 네이티브 코드로 바꿔두고 다음번부터는 컴파일 된 코드를 바로 사용한다.
- GC (Garbage Collector) : 참조되지 않고 있는 객체를 정리하는 역할을 한다. GC는 성능과 관련있게 중요하므로 깊게 알아둘 필요가 있음. 추후 글로 정리할 예정.
JNI (Java Native Interface)
우선 native란, java가 아닌 C, C++, 어셈블리어로 작성된 함수를 사용할 수 있는 방법을 제공하는 것으로, native 키워드를 붙여 사용할 수 있다. 위의 그림에서 볼 수 있듯이, 네이티브 메소드 라이브러리에 메소드들이 저장되어 있고 이를 사용하기 위해서 제공되는 인터페이스인 JNI를 사용해야한다.
Ex)
Thread.currentThread();
는 네이티브 메소드이다.@HotSpotIntrinsicCandidate public static native Thread currentThread();
'Programming > Java' 카테고리의 다른 글
[JAVA] java, JVM, JDK, JRE란? (0) | 2020.11.25 |
---|---|
[자바 프로그래밍] Lesson6 상속 (inheritance) (0) | 2020.10.04 |
[자바 프로그래밍] Lesson5 인터페이스와 폴리모피즘 (0) | 2020.09.03 |
[자바 프로그래밍] Lesson4 클래스 메소드와 필드 (0) | 2020.08.30 |
[자바 프로그래밍] Lesson3 문자열과 패키지 (0) | 2020.08.29 |