dev-sohee 님의 블로그

자바의 두뇌 : JVM의 모든 것 본문

java

자바의 두뇌 : JVM의 모든 것

dev-sohee 2024. 7. 6. 13:55

이 글에서는 자바의 핵심이자 두뇌인 JVM에 대해 깊이 알아보겠습니다.

출처_https://ko.wikipedia.org

 

* 클래스 로더
* 실행 엔진
* 메모리 영역

 

# 클래스 로더

자바는 동적 로드, 즉 컴파일 타임이 아니라 런타임(바이트 코드를 실행할 때)에 클래스를 로드하고 링크한다는 특징이 있습니다. 이 동적 로드를 담당하는 부분이 JVM의 클래스 로더입니다. 정리하자면, 클래스 로더는 런타임 중에 JVM의 메서드 영역에 동적으로 Java 클래스를 로드하는 역할을 합니다.

**바이트코드란? : 자바 소스 코드를 컴파일한 결과로 생성되는 중간 코드. 자바 소스 파일(.java)을 자바 컴파일러(javac)에 의해 바이트코드(.class)로 변환한다. 사람이 읽기 쉽도록 쓰인 소스코드와 비교했을 때, 바이트 코드가 덜 추상적이며, 더 간결하고, 더 컴퓨터 중심적이다.

 

클래스 로더의 기능

1. 로딩(Loading)

- 자바 바이트 코드(.class)를 메서드 영역에 저장합니다.

- 각 자바 바이트 코드(.class)는 JVM에 의해 메서드 영역에 아래의 정보들을 저장합니다.

1) 로드된 클래스를 비롯한 그의 부모 클래스의 정보

2) 클래스 파일과 Class, Interface, Enum의 관련 여부

3) 변수나 메서드 등의 정보

2. 링킹(Linking)

- 검증(Verification): 클래스 파일이 JVM에서 실행되기 적합한지 검증하는 단계입니다. Java 언어 명세와 JVM 명세를 준수하는지 확인하고 형식, 구조, 구문 규칙 등을 검사하여 클래스 파일이 유효하고 무해한지 확인합니다.

- 준비(Preparation): 클래스의 정적 변수에 대한 메모리를 할당하고, 이를 기본값으로 초기화하는 단계입니다. 정적 변수는 기본적으로 0(정수형), 0.0(실수형), null(참조형)으로 초기화됩니다.

- 분석(Resolution): 클래스, 인터페이스, 필드, 메서드 등의 심볼릭 레퍼런스를 실제 메모리 주소로 변경하는 단계입니다. 이 단계는 선택적일 수 있으며, 심볼릭 레퍼런스를 실제 레퍼런스로 변환하는 작업을 수행합니다.

3. 초기화(Initialization)

- 초기화 단계에서는 클래스의 정적 변수를 실제 값으로 설정하고, 정적 초기화 블록을 실행합니다. 예를 들어, 클래스에 static int x = 10; 과 같은 변수가 있다면, 이 변수는 초기화 단계에서 10으로 설정됩니다.

 

클래스 로더의 종류

자바의 클래스들은 자바 프로세스가 새로 초기화되면 아래의 클래스로더가 차례차례 로딩되며 작동합니다.

1. 부트스트랩 클래스 로더(Bootstrap Class Loader)

- JVM 시작 시 가장 최초로 실행되는 클래스로더입니다. 부트스트랩 클래스로더는 자바 클래스를 로드하는 것이 아닌, 자바 클래스를 로드할 수 있는 자바 자체의 클래스로더와 최소한의 자바 클래스(java.lang.Object, Class, Classloader)만을 로드합니다. JAVA8 기준으로 ${JAVA_HOME}/jre/lib에 위치한 자바 런타임 코어 클래스를 로드합니다.

2. 확장 클래스 로더(Extension Class Loader)

- 부트스트랩 클래스 로더의 자식 로더로, 확장 자바 클래스들을 로드한다. java.ext.dirs 환경 변수에 설정된 디렉토리의 클래스 파일을 로드하고, 이 값이 설정되어 있지 않은 경우 ${JAVA_HOME}/jre/lib/ext에 있는 클래스 파일을 로드합니다.

3. 애플리케이션 클래스 로더(Application Class Loader)

- 확장 클래스 로더의 자식 로더로, 애플리케이션 클래스패스(Classpath)에 지정된 디렉토리와 JAR 파일에서 클래스를 로드합니다.

 

 

# 실행 엔진

자바 실행 엔진(Java Execution Engine)은 JVM의 핵심 구성 요소 중 하나로,

자바 바이트코드(.class 파일)를 실행하고 자바 프로그램을 실제로 구동하는 역할을 합니다.

실행 엔진은 자바 바이트코드를 기계어로 변환하여 CPU가 실행할 수 있게 하며,

다양한 최적화 기법을 통해 효율적인 실행을 보장합니다.

 

자바 실행 엔진의 구성 요소

1. 인터프리터(Interpreter)

- 자바 바이트코드를 한 줄씩 읽어서 실행합니다. 바이트코드를 빠르게 시작할 수 있지만, 실행 속도가 비교적 느립니다. 주로 초기 실행 단계에서 사용되며, 반복 실행되는 코드에서는 비효율적일 수 있습니다.

**인터프리터 언어가 정적 컴파일 언어보다 실행속도가 느린이유: 인터프리터 언어는 기계어로 미리 변환해두지 않고 프로그램 실행 도중 변환하기 때문에 정적 컴파일 언어보다 실행속도가 느립니다.

2. JIT 컴파일러(Just-In-Time Compiler)

- JIT 컴파일러는 프로그램 실행 중에 코드를 동적으로 컴파일하는 기술입니다.

전통적인 컴파일러와는 달리, JIT 컴파일러는 프로그램을 실행할 때 코드의 일부 또는 전체를 컴파일합니다. 이는 프로그램의 실행 속도를 최적화하고, 런타임 시 발생하는 다양한 상황에 더 잘 대응할 수 있게 합니다.

(JIT 컴파일러는 매우 중요한 내용이므로 'JVM의 부스터, JIT-Compiler'에서 자세히 다루겠습니다.)

3. 가비지 컬렉터(Garbage Collector)

더 이상 사용되지 않는 객체를 자동으로 메모리에서 해제하여 메모리 누수를 방지합니다.

(가비지 컬렉터도 매우 중요하므로 'JVM의 청소부, GC(Garbage Collector)'에서 자세히 다루겠습니다.)

 

4. 런타임 데이터 영역(Runtime Data Area)

출처_https://velog.io

 

- 자바 프로그램이 실행되는 동안 필요한 데이터를 저장하는 메모리 영역입니다. 주요 구성 요소는 다음과 같습니다.

1) 메서드 영역(Method Area): 클래스 정보, 메서드, 필드, 상수 풀 등을 저장합니다.

2) 힙(Heap): 객체가 동적으로 할당되는 영역입니다.

3) 스택(Stack): 각 스레드마다 할당되며, 메서드 호출 시 프레임을 저장합니다.

4) PC 레지스터(Program Counter Register): 각 스레드마다 할당되며, 현재 실행 중인 명령어의 주소를 저장합니다.

5) 네이티브 메서드스택(Native Method Stack): 네이티브 메서드 호출에 사용되는 스택입니다.

 

자바 실행 엔진 동작 과정

1. 로딩(Loading): 클래스 로더가 .class 파일을 메모리에 로드합니다.

2. 링킹(Linking): 로드된 클래스 파일을 검증하고, 기본값으로 초기화하며, 심볼릭 레퍼런스를 실제 메모리 주소로 변환합니다.

3. 초기화(Initialization): 클래스의 정적 변수를 실제 값으로 설정하고, 정적 초기화 블록을 실행합니다.

4. 실행(Execution): 인터프리터와 JIT 컴파일러가 협력하여 자바 바이트코드를 실행합니다.

 

자바 실행 엔진은 자바 애플리케이션의 성능과 효율성을 결정짓는 핵심 요소입니다.

인터프리터와 JIT 컴파일러의 조합은 초기 실행 속도와 반복 실행 성능을 균형 있게 제공하고 가비지 컬렉터는 메모리 관리를 자동화하여 개발자가 메모리 관리에 신경 쓰지 않고도 안정적인 프로그램을 작성할 수 있게 합니다.

 

 

# 메모리 영역

부팅된 JVM은 목적 파일을 받아 실행하고 제일 먼저 전처리 과정을 수행합니다. 전처리를 이해하기 위해 반드시 알아야 할 것이 바로 T 메모리 구조입니다. 모든 프로그램이 메모리를 사용하는 방식은 그림과 같습니다.

 

그런데 객체 지향 프로그램에서는 데이터 저장 영역이 다시 세개의 영역으로 분할되어 사용됩니다. 이것을 T 메모리 구조라고 합니다.

T메모리 구조

 

T메모리의 구성 요소는 다음과 같습니다.

1. 스태틱 영역 - 클래스들의 놀이터

2. 스택 영역 - 메서드들의 놀이터

3. 힙 영역 - 객체들의 놀이터

 

모든 자바 프로그램이 반드시 포함하는 java.lang이라고 하는 패키지가 있는데 JVM은 가장 먼저 이 java.lang 패키지를 스태틱 영역에 가져다 놓습니다.

<데이터 영역 T 메모리>

 

다음으로 개발자가 작성한 모든 클래스와 패키지 역시 스태틱 영역에 가져다 놓습니다. 여기까지가 JVM의 전처리 과정입니다.

<JVM의 전처리 완료 후 메모리>

 

전처리가 완료됬으니 이제 main()메서드를 실행할 차례입니다. main()메서드의 여는 중괄호를 만날 때마다 메모리 상의 스택 영역에는 스택 프레임이 하나씩 생성됩니다.

main()메서드 스택 프레임과 인자 변수 공간

 

main() 메서드의 코드 문장들을 처리한 뒤 닫는 중괄호를 만나면 JRE는 JVM을 종료하고 JRE 자체도 운영체제의 메모리에서 사라지게 됩니다.

 

결론적으로 자바라는 언어는 JVM 덕분에 "한 번 작성하고 어디서나 실행 가능(Write Once, Run Anywhere)" 하며

다양한 운영체제와 하드웨어에서 동일한 바이트코드가 실행된다는 우수한 플랫폼 독립성을 지닌 언어로 자리매김할 수 있었던 것 같습니다.