JAVA (1) - JVM 구조와 자바 실행 원리
1. 자바 실행 과정 (Java Code to Execution)
자바는 “Write Once, Run Anywhere (한 번 작성하면 어디서든 실행된다)”라는 슬로건을 가진 대표적인 플랫폼 독립적 언어다. 이를 가능하게 만드는 것이 바로 컴파일 과정과 JVM이다. 자바 소스 코드가 작성된 후 실행되기까지의 전체 흐름은 다음과 같다.
- 소스 코드 작성 (
.java): 개발자가 자바 문법에 맞추어 코드 파일을 작성한다. - 자바 컴파일러(
javac) 컴파일:javac명령어를 통해 소스 코드를 읽어 자바 바이트코드(Bytecode,.class) 파일로 변환한다. 바이트코드는 아직 컴퓨터가 직접 이해할 수 없는, JVM이 이해할 수 있는 중간 단계의 기계어다. - 클래스 로더(Class Loader) 로딩: 작성된
.class파일들을 JVM 내부로 로딩하고 링크 및 초기화를 거쳐 메모리(Runtime Data Area)에 적재한다. - 실행 엔진(Execution Engine) 해석: JVM의 실행 엔진이 메모리에 올려진 바이트코드를 OS가 이해할 수 있는 네이티브 기계어로 해석하고 실행한다.
2. JVM 내부 구조 (JVM Architecture)
JVM은 크게 클래스 로더 System, 런타임 데이터 영역(Runtime Data Area), 실행 엔진(Execution Engine), 네이티브 인터페이스(JNI/Native Method Libraries)로 구성된다.
① 클래스 로더 (Class Loader)
동적 로딩(Dynamic Loading)을 담당하는 주체다. 자바는 컴파일 시점에 모든 클래스를 로딩하는 것이 아니라, 애플리케이션 실행 중 필요한 시점에 .class 파일을 읽어 메모리에 적재한다.
- Loading: 클래스 파일을 가져와 JVM 메모리에 올린다.
- Linking: Verify(검증) $\rightarrow$ Prepare(메모리 할당) $\rightarrow$ Resolve(심볼릭 참조를 실제 메모리 주소로 변경) 과정을 진행한다.
- Initialization: static 변수 및 블록을 초기화한다.
② 런타임 데이터 영역 (Runtime Data Area)
JVM이 OS로부터 할당받은 메모리 공간이다. 목적에 따라 5가지 주요 영역으로 나뉘며, 스레드 간 공유 여부가 매우 중요하다.
모든 스레드가 공유하는 영역
- Method Area (메서드 영역): 클래스 구조 정보(클래스명, 부모 클래스명, 메서드/필드 데이터), static 변수, Constant Pool(상수 풀) 등이 저장된다. JVM 시작 시 생성된다.
- Heap Area (힙 영역):
new키워드로 생성된 모든 객체(Instance)와 배열이 저장되는 영역이다. 가비지 컬렉터(Garbage Collector)의 주요 관리 대상이다.
스레드마다 독립적으로 생성되는 영역
- JVM Stack (JVM 스택): 메서드가 호출될 때마다 Frame이라는 단위가 쌓인다. 프레임 내부에는 지역 변수, 매개변수, 연산 스택, 리턴 값 등이 보관된다. 메서드가 종료되면 해당 프레임은 스택에서 pop되어 제거된다.
- PC Register (Program Counter Register): 현재 스레드가 실행 중인 JVM 명령의 메모리 주소를 가리킨다.
- Native Method Stack: 자바 외의 언어(C, C++ 등)로 작성된 네이티브 코드를 실행할 때 사용되는 스택이다.
③ 실행 엔진 (Execution Engine)
메모리에 적재된 바이트코드를 실행하는 주체다.
- Interpreter (인터프리터): 바이트코드를 한 줄씩 읽어서 기계어로 해석하고 실행한다. 초기 실행 속도는 빠르지만, 동일한 코드가 반복 호출되어도 매번 재해석하므로 전반적인 실행 속도가 느리다.
- JIT Compiler (Just-In-Time Compiler): 인터프리터의 단점을 보완하기 위해 도입되었다. 자주 실행되는 코드(Hot Spot)를 감지하여 바이트코드 전체를 네이티브 기계어로 미리 컴파일해두고, 이후에는 해석 없이 직접 기계어를 실행한다.
- Garbage Collector (GC): Heap 영역에서 더 이상 참조되지 않는 객체들을 탐색하여 메모리에서 해제한다.
④ JNI & Native Method Libraries
- JNI (Java Native Interface): C, C++ 등의 네이티브 라이브러리와 상호작용할 수 있는 인터페이스를 제공한다.
- Native Method Libraries: C/C++로 구현된 라이브러리 모음이다.
3. WORA(Write Once, Run Anywhere)의 핵심 원리
C/C++ 같은 컴파일 언어는 소스 코드를 특정 OS(Windows, Linux, macOS)에 맞춰 직접 기계어로 컴파일한다. 따라서 OS마다 컴파일 결과물이 달라지고 바이너리가 타깃 플랫폼에 귀속된다.
반면 자바는 OS에 독립적인 중간 단계인 바이트코드를 생성한다. 그리고 각 OS 환경에 맞게 제작된 JVM이 이 바이트코드를 받아 해당 OS의 기계어로 번역해 실행한다. 즉, OS 종속적인 작업은 JVM이 전담하므로 개발자는 플랫폼에 신경 쓰지 않고 동일한 바이트코드만 배포하면 어디서든 동일하게 동작시킬 수 있다.
4. 핵심 요약
| 구성 요소 | 역할 및 핵심 기능 |
|---|---|
javac | .java 소스 코드를 .class 자바 바이트코드로 컴파일한다. |
| Class Loader | 바이트코드 파일들을 런타임 시점에 동적으로 메모리에 올린다. |
| Method Area | 클래스 메타 데이터, static 변수를 저장하며 모든 스레드가 공유한다. |
| Heap | 동적으로 생성된 객체가 저장되는 영역으로, GC의 관리를 받는다. |
| Stack / PC / Native | 각 스레드마다 독립적으로 존재하며 메서드 호출 정보 및 실행 상태를 관리한다. |
| JIT Compiler | 자주 쓰이는 바이트코드를 네이티브 기계어로 변환하여 실행 성능을 끌어올린다. |
