개요
이 글에서는 JVM 명세와 JVM의 구조에 대하여 서술된 책을 참고로 JVM을 이루는 구성요소들의 역할들에 대하여 알아보려고 합니다. 아래 책을 참고하였습니다. Inside the Java 2 Virtual Machine: Venners, Bill
Inside the Java 2 Virtual Machine
Update of the best book on the Java Virtual Machine the only one praised by JavaWorld as "excellent" and "much stronger" than any competition. Thorough revision with all-new material covering the just-released version that rockets Java into the big league.
www.amazon.com
JVM이란
자바 가상 머신(영어: Java Virtual Machine, JVM)은 class 파일을 읽어 실행할 수 있는 추상화된(가상의) 컴퓨터이다. 출처 - 위키백과
JVM은 JVM 명세를 만족시키는 모든 구현체를 의미하는데, 이는 다시 말하면 JVM이 만족시켜야하는 추상화된 명세가 존재한다는 것 입니다. 이 JVM 명세는 JCP 홈페이지를 찾아보면 찾을 수 있고, Oracle 홈페이지에도 Java 버전별로 잘 정리되어 있습니다.

벤더사들이 구현한 수많은 JVM 구현체가 존재하며, 이 구현체들은 각각 서로다른 특징도 가지고 있습니다. 그리고,
모든 자바 어플리케이션은 추상화된 JVM의 명세를 따르는 구현체의 런타임 인스턴스안에서 시작됩니다.
JVM의 생애주기
Java 앱이 실행되는 순간에, JVM 런타임 인스턴스는 생성되며, Java 앱이 동작을 완료하는 순간, 인스턴스는 사라집니다.
만약, 하나의 컴퓨터에서 동일한 구현체로 3개의 자바 앱을 동시에 실행한다면, 서로다른 JVM 인스턴스 3개에서 각 Java앱이 동작합니다.
JVM의 구조
이제 JVM의 내부 구조를 알아봅시다. JVM 명세서에서는 크게 3가지 구성요소로 나누어 JVM의 동작을 명세하고 있습니다.

- 클래스 로더 시스템
- 메모리 영역
- 데이터 타입, 명령어(실행엔진 관련된 부분)
이 중에서 class 파일들을 JVM에 로딩하주는 클래스 로더 시스템과 Java 앱을 실행하기 위하여 필요한 메모리 영역에 대해서 알아 보겠습니다.
Class loader subsystem
javac를 통해 만들어지는 .class 파일들을 JVM에 로딩해주는 역할을 맡고 있는 JVM의 구성요소
기본 클래스 파일(main 메소드를 포함하는 클래스)이 JVM으로 전달된 다음 코드가 실행되기 전 3가지 단계를 거치게 됩니다.
- 로딩
- .class 파일을 읽고 타입의 바이너리 데이터로 만들어 메소드 영역에 저장
- 링킹
- 검증 - 가져온 .class파일이 유효한지 체크
- 준비 - 클래스 변수(static 변수)에 대한 메모리 할당 및 기본값으로 초기화
- 해결(optional) - 심볼릭 메모리 레퍼런스를 실제 레퍼런스로 변환
- 초기화
- static 변수의 값을 할당 (static block이 있다면 이때 실행)
다시 정리하면, Class loader는 클래스의 바이너리 데이터를 찾고 파싱하며, class 파일을 검증하고, 클래스 변수에 대한 메모리를 할당하고 초기화하며, symbol 참조값 해결에도 도움을 줘야 합니다.
Runtime Data Area
JVM이 프로그램을 수행하기 위해 운영체제로부터 할당 받는 메모리 영역입니다.

위 그림을 보면 알 수 있듯 Runtime Data Area는 Thread별로 생성 되는 영역과 Thread 간 공유하는 영역으로 분류됩니다.
Thread 별로 생성되는 영역
- PC Register
- 스레드가 Java 메서드(네이티브가 아닌)를 실행하면, pc 레지스터는 다음 실행해야할 JVM Instruction 주소 값을 가르킵니다.
- Register Based 인 CPU 내의 PC와는 다르게 Stack-Based 로 동작합니다.
- 이는 WORA 원칙을 지켜야하는 Java 언어의 특성에 기반한 것으로 어떤 하드웨어 위에서 동작할지 모르기 때문에, 하드웨어에 의존성이 생기는 구조 레지스터 기반이 아닌 스택 기반으로 동작합니다.
- JVM Stack
- 스레드가 실행하는 Java 메서드(네이티브가 아닌)의 호출 상태를 저장합니다.
- 메서드의 매개변수, 반환 값, 로컬 변수등이 담기며 이는 곧, 하나의 메서드안에서 지역변수의 동시성 문제를 걱정하지 않아도 된다는 의미입니다.
- Native Method Stack
- Java외의 언어로 작성된 Native Code를 위한 별도의 Stack입니다.
- 구현하기에 따라 레지스터에 저장할 수 도 있습니다.
Thread간 공유되는 영역
Thread간 공유되는 영역은 다시말하면 Thread-Safe가 보장되어야 하는 영역으로 볼 수 있습니다.

- Method Area
- JVM에서 로드된 타입(클래스)에 대한 정보를 저장합니다.
- Runtime Constant Pool과 Static 변수등
- JVM의 다른 메모리 영역에서 해당 정보에 대한 요청이 오면, 실제 물리 메모리 주소로 변환하여 전달하며, 이를 Constant Pool Resolution이라고 합니다.
- GC의 대상이 되는 영역
- Heap
- 런타임에서 생성되는 모든 객체들이 저장됩니다.
- GC의 대상이 되는 영역
데이터 타입과 표현 방식의 자유
메서드 영역에 저장되는 타입정보들
- 타입의 전체 이름 (fully qualified name)
- 타입의 직접 상위 클래스의 전체 이름 (인터페이스나 java.lang.Object 클래스인 경우 상위 클래스가 없는 경우 제외)
- 타입이 클래스인지 인터페이스인지 여부
- 타입의 접근제한자와 같은 키워드 (public, abstract, final와 같은)
- 직접 상위 인터페이스의 전체 이름
표현 방식의 자유
Java Virtual Machine 구현이 타입 정보를 내부적으로 어떻게 표현하는지는 구현 디자이너의 결정입니다. 예를 들면, 클래스 파일의 바이트 스트림값을 어디부터 읽을지가 있습니다.

자바 코드를 실행할 때 JVM에서 일어나는 일들
실행할 java 소스 코드
package com.example;
public class Main {
public static void main(String[] args) {
Jay jay = new Jay();
jay.work();
}
}
package com.example;
public class Jay {
private int age = 26;
public void work() {
}
}
JVM에서 일어나는 일
#0. JVM은 Class Path를 탐색하여 Main.class 파일을 찾아 읽는다.
#1. 클래스 로더는 해당 클래스 정보를 메서드 영역에 올린다.
#2. JVM은 메서드 영역에 저장된 바이트코드를 해석하여 main() 메서드를 호출한다.
#3. JVM이 main()을 실행하는 동안 현재 클래스 (class Main)의 Constant Pool을 가리키는 포인터를 유지
⇒ 바이트 코드를 실행하면서 Constant Pool을 언제든 직접 참조하기 위함
#4. Jay 클래스의 구현체를 만들기 위하여 Jay 클래스 정보를 로드한다.
=> 해당 클래스 정보가 필요하기 전까진 먼저 로드하지 않는다.
#5. main 메서드의 첫번째 명령어는 JVM에게 Constant Pool에 있는 클래스(Jay)를 위한 메모리 공간을 할당할 것을 명령한다.
#6. JVM은 같은 메서드 영역에 로드된 Jay 클래스 정보를 참고하여 Heap영역에 공간을 할당한다.
#7. 인스턴스 변수 값(age) 기본 초기값(0)로 초기화하고 Heap영역에 새롭게 만들어진 Jay Instance에 대한 참조를 스택에 푸시한다.
#8. 이 참조를 사용하여 age 변수를 올바른 초기값인 26으로 초기화
Constant Pool
클래스 내에 사용되는 상수들을 담은 곳

#8은 인덱스 10번을 가르키고 있고, 인덱스 10에는 com/example/Jay라는 값이 담겨있습니다.
#10의 타입은 Utf8(String literal)로 타입의 전체 이름 (fully qualified name)을 담고 있습니다.
이렇게 참조하는 대상의 이름만을 지칭하는 것을 심볼릭 레퍼런스(symbolic reference)라고 합니다.
Constant Pool Resolution과정을 거쳐 실제 주소값으로 변환됩니다.
#15를 보면, main 메서드에서 호출하고 있었던 Jay의 work 메서드도 담겨있는 것을 알 수 있습니다.
마무리
다음글에서는 이번 글에서 다루지 못했던 Garbage Collection과 런타임 데이터 영역 중 Heap 메모리에 대해서 조금 더 알아보도록 하겠습니다.
'개발' 카테고리의 다른 글
| Redis의 자료구조 (List, Sorted Set) (0) | 2025.01.19 |
|---|---|
| SOLID Principle 알아보기 - 두번째 (0) | 2025.01.05 |
| JVM 두번째 글 - Heap 메모리 구조에 대하여 (0) | 2024.12.22 |
| SOLID Principle 알아보기 - 첫번째 (0) | 2024.11.24 |
| 다양한 페이징 기법(pagination)과 장단점 (1) | 2024.10.13 |