본문 바로가기
학습

JVM의 구조

코동이 2022. 10. 21.

개요


자바는 JVM에서 작동하는 프로그램으로, JVM의 내부 구조를 알아보겠습니다.

 

 

JVM 설계도


JVM은 크게 클래스 로더 시스템(Class Loader SubSystem),  런타임 데이터 공간(Runtime Data Areas), 실행 엔진(Execution Engines) 3가지로 분류 할 수 있습니다. 클래스 파일을 읽는 것부터, 분석과 실행의 과정을 확인해보겠습니다.

 

아래 사진은 JVM의 설계도입니다.

 

 

1. 클래스 로더 시스템(Class Loader SubSystem)


  클래스 로더 시스템은 자바 클래스를 JVM의 Runtime Data Area에 동적으로 로드합니다. 컴파일 시점이 아닌 런타임 시점에 참조하고 있는 클래스를 로딩, 링킹, 초기화를 통해 로드합니다. 클래스 로더 덕분에 런타임에 파일 시스템이나 파일에 대해서 알 필요가 없습니다. 모든 클래스들이 단 하나의 클래스 로더로 로드되지 않습니다. 클래스들의 경로와 종류에 따라서 달라지며 getClassLoader() 메소드로 결정합니다. 적합한 클래스를 찾지 못하면 NoClassDefFoundErrorClassNotFoundException을 던집니다.

 

 특히 파일 형태로 존재하던 클래스를 런타임에 Type으로 만듭니다.

 

①Loading


로딩(Loading)은 말 그대로 바이너리 형태의 타입(클래스)을 JVM 안으로 불러옵니다. BootStrap ClassLoader, Extension ClassLoader, System ClassLoader 3가지가 있습니다.

 

1. BootStrap ClassLoader

 부트스트랩 클래스로더(BootStrap ClassLoader)는 부트스트랩 클래스패스에 있는 클래스를 로드합니다.(rt.jar) 우선순위가 가장 높습니다.

 

2. Extension ClassLoader

 실행 클래스로더(Extension ClassLoader)는 부트스트랩 클래스로더의 자손으로 JDK Extension 라이브러리의 jre/lib/ext 디렉토리에 있는 파일들을 로드합니다.

 

3. System ClassLoader

 시스템 클래스로더(System ClassLoader)는 실행 클래스로더의 자손으로 환경변수 경로, 어플리케이션 수준 클래스패스 등을 로드합니다.

 

아래 사진은 클래스 로더의 동작 관계도입니다.

 

https://www.geeksforgeeks.org/classloader-in-java/

 

 

②Linking


 링킹(Linking)은 로딩 된 타입을 런타임의 바이너리 타입 데이터로 구성합니다.

 

1. Verify

 Verify는 바이트 코드 검사기가 생성된 바이트 코드가 정상인지 검사하며 검증이 실패하면 검증 에러를 냅니다.

 

2. Prepare

 Prepare는 모든 static 변수들과 메모리가 기본 값으로 할당됩니다. 타입이 필요로 하는 메모리를 할당합니다.

 

3. Resolve

 Resolve는 모든 심볼릭 메모리 참조를 메소드 영역(Method Area)에 있는 기본 참조로 변경합니다. 즉 실제 메모리 주소 값으로 변경합니다.

 

 

③Initialization


Initialization은 클래스 로딩의 마지막 과정으로, 모든 static 변수들을 해당 값으로 초기화하며, static 블록을 실행합니다.

 

 

 

 

 

2. Runtime Data Area


 런타임 데이터 영역(Runtime Data Area)은 JVM의 메모리 영역으로 어플리케이션 실행을 위한 데이터들이 적재되어 있습니다. 아래 사진처럼 Method Area, Heap Area, Stack Area, PC register, Native Method Stack 5가지로 나뉩니다.

 

https://jeong-pro.tistory.com/148

 

 

Stack, PC register, Native Method Stack는 각 쓰레드마다 하나씩 생성되기 때문에 아래의 그림처럼 여러개가 존재할 수도 있습니다.

 

https://hongsii.github.io/2018/12/20/jvm-memory-structure/

 

 

①Method Area


 메서드 영역(Method Area)는 모든 쓰레드들이 공유하는 메모리영역으로 JVM이 가동할 때 생성이 되며 가비지 컬렉터의 대상이 됩니다.

 

https://m.blog.naver.com/2000yujin/130156863890

 

  • Constant Pool

Type의 모든 Constant 정보들이 있다. 상수를 의미하는 Literal Constant, Member Variable, Class variable, Method로의 모든 Symbolic Reference까지 확장한 개념이다. JVM은 실행 시 참조하는 객체에 접근할 필요가 있으면 Constant Pool의 Symbolic Reference를 통해 해당 객체가 위치한 메모리 주소를 찾아 동적으로 연결한다.

 

  • Field Information

public, private, protected, static, final, volatile, transient와 같은 Field의 접근자가 위치한다.

 

  • Method Information

모든 Method의 정보를 의미한다. 메소드 이름, 반환 Data Type, Parameter 수와 Data Type, 선언 순서드이 있으며 Field Information 과 같은 public, private, protected....등의 Method 접근자가 위치한다.

 

  • Class Variable

static으로 선언된 변수이며 Class에서 하나의 값으로 유지된다. 이 변수는 모든 객체들에 공유되며 Instance 없이도 접근이 가능하다. Class 사용하기 전부터 Method Area에 미리 메모리를 할당 받는다.

 

 

 

②Heap Area


 힙 영역(Heap Area)에는 모든 객체들과 객체에 대응하는 인스턴스 변수와 배열들이 저장됩니다. JVM마다 1개의 힙 영역이 있습니다. 멀티 쓰레드가 메서드 영역(Method Area)와 힙 영역(Heap Area)을 공유하므로 데이터는 쓰레드에 안전하지 않습니다.

 

③Stack Area


 스택 영역(Stack Area)은 모든 쓰레드마다 런타임에 생성됩니다. 메서드 호출이 있을 때마다, 스택 프레임(Stack Frame)이라 불리는 스택 공간에 하나의 항목이 저장됩니다. 모든 지역 변수들이 스택 메모리에 생성됩니다. 스택 영역은 공유되지 않기 때문에 쓰레드에 안전합니다. 또한 스택 프레임은 하위 3가지로 나뉩니다.

 

 

 

 

PC(Program Counter) Rgister


https://m.blog.naver.com/PostView.nhn?blogId=2000yujin&logNo=130156226754&proxyReferer=https:%2F%2Fwww.google.com%2F

 

 PC register는 현재 실행중인 명령어의 주소를 저장하는 곳입니다. 메서드를 실행하고 있다면 Native Pointer와 Return Address를 가지고 있으며 현재의 명령어 주소를 가지고 있습니다. 수행중인 쓰레드가 Native Method이면 undefined 상태로 아무 값도 할당되지 않게 된다.

 

 

Native Method Stack


https://m.blog.naver.com/PostView.nhn?blogId=2000yujin&logNo=130156226754&proxyReferer=https:%2F%2Fwww.google.com%2F

 

 Native Method Stack은 Java 외의 언어로 작성된 프로그램으로 API 툴킷 등과 통합을 쉽게 하기 위해 JNI(Java Native Interface) 표준 규약을 제공하고 있습니다. 즉, 자바 프로그램에서 네이티브 코드로 되어 있는 함수를 호출 할 수도 있고 결과 값을 받아올 수도 있습니다. Native Method가 호출되면 현재 스택에서 나와 해당 쓰레드의 스택에서 작동을 하고 다시 본래의 스택으로 돌아갑니다.

 

Hopspot JVM의 경우 Stack 영역에 구분을 두지 않고 Native Stack으로 통합하여 사용합니다.

 

 

3. Execution Engine


 실행 엔진(Execution Engine)은 런타임 데이터 영역에 할당된 바이트 코드를 실행합니다.실행엔진은 바이트 코드를 읽고 각각을 실행합니다.

 

①Interpreter


 인터프리터(Interpreter)는 바이트코드를 빠르게 해석하지만 실행이 느립니다. 하나의 메서드가 여러번 호출되면, 호출때마다 새로운 해석(interpretation)을 해야 합니다. 한줄씩 해석되고 실행되기 때문에, 중간에 오류가 생기면 인터프리터는 작업을 더이상 작동하지 않고 멈춥니다. 

 

 

②JIT Compiler


  JIT 컴파일러(JIT Compiler)는 인터프리터를 보완하기 위해 만들어졌습니다. 실행 엔진은 바이트 코드를 변환하는데 인터프리터를 사용하지만 여러번 반복되는 코드를 발견하면 JIT 컴파일러를 사용합니다. JIT 컴파일러는 모든 바이트코드를 컴파일하고 순수코드(native code)로 바꿉니다. 따라서 컴파일  총 시간을 줄입니다. 순수 코드는 메소드를 반복 호출 할 때 사용합니다. 다 사용하고 나서는 캐시에서 삭제되고 인터프리터 방식으로 전환합니다.

 

1. Intermediate Code Generator - 중간언어를 만듭니다.

2. Code Optimizer - 중간언어를 최적화합니다.

3. Target Code Generator - 기계어 혹은 순수 코드를 만듭니다..

4. Profiler - 메소드가 여러번 호출되는지 아닌지 hotspot을 찾는 특별한 컴포넌트입니다.

 

 

③JNI(Java Native Interface)


JNI는 Native Method Libraries와의 상호소통을 하며 실행 엔진에 필요한 Native Libraries를 제공합니다.



④Native Method Libraries


 Native Method Libraries는 실행 엔진에 필요한 Native Libraries를 제공합니다.

 

 

 

참고

https://dzone.com/articles/jvm-architecture-explained

https://jeong-pro.tistory.com/148

https://honbabzone.com/java/java-jvm/

https://www.slipp.net/wiki/pages/viewpage.action?pageId=8880262

반응형