🎯 개요
Java 프로그램은 단순히 .java
파일을 실행하는 것처럼 보이지만, 실제로는 JDK -> 컴파일러 -> JVM -> 메모리 영역 -> 실행 엔진에 이르기까지 여러 단계를 거쳐 실행됩니다.
이 글에서는 Java 실행 구조를 컴파일 시점부터 실행 시점까지 전체 흐름으로 정리해보았습니다.
✅ 주요 실행 흐름 요약
단계 | 설명 |
1. 작성 | 개발자가 .java 파일을 작성 |
2. 컴파일 | javac (JDK 내 컴파일러)가 .class 바이트코드로 변환 |
3. 클래스 로딩 | JVM의 ClassLoader가 .class 파일을 메모리로 로딩 |
4. 메모리 배치 | JVM 런타임 메모리 영역(Method Area, Heap, Stack 등) 배치 |
5. 실행 엔진 처리 | 바이트코드를 인터프리터 또는 JIT 컴파일러로 실행 |
6. 결과 출력 | 프로그램 실행 결과가 콘솔 또는 UI로 출력 |
1. .java
→ .class
컴파일 과정
// HelloWorld.java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
작성된 자바 코드는 다음 명령으로 컴파일 됩니다:
javac HelloWorld.java
- javac는 JDK(Java Development Kit)에 포함된 컴파일러
- .java -> .class 바이트코드 생성
- 이 .class 파일은 JVM이 이해할 수 있는 코드
2. JVM 구조와 실행 흐름
.class 파일이 만들어졌다면 이제 JVM이 실행을 맡습니다.
JVM 내부 주요 구성
구성 요소 | 설명 |
ClassLoader | .class 파일을 JVM 메모리로 로드 |
Runtime Data Area | 실행 시 필요한 메모리 공간 |
Execution Engine | 바이트코드 해설/실행 (인터프리터 + JIT) |
Native Interface | C/C++로 작성된 라이브러리와 연결 |
Native Method Library | OS 기반 라이브러리 집합 |
3. ClassLoader의 로딩, 링크 초기화
JVM의 클래스로더는 다음 3단계로 클래스를 처리합니다.
단계 | 설명 |
로딩(Loading) | .class 파일을 메모리에 로드 |
링크(Linking) | 검증(Verify): 유효한 바이트코드인지 검사 준비(Prepare): static 변수 공간 확보 및 기본값 설정 해결(Resolve): 심볼릭 레퍼런스를 실제 메모리 주소로 변경 |
초기화(Initialization) | static 블록 및 변수 초기화 코드 실행 |
4. JVM 메모리 구조
클래스가 로딩되면 JVM 메모리에 다음과 같이 배치됩니다:
메모리 영역 | 설명 |
Method Area | 클래스의 메타데이터, static 변수, 상수 정보 |
Heap | 객체(instance), 배열 등이 저장됨 |
Stack | 메서드 호출 시 생성되는 프레임 (지역 변수, 매개변수, 리턴주소 등) |
PC Register | 현재 실행 중인 JVM 명령의 주소 저장 |
Native Method Stack | C/C++ native 메서드 호출 정보 저장 |
- 참고: Java 8 이후 PermGen이 제거되고 Metaspace가 Method Area를 대체
5. Execution Engine 처리
JVM은 바이트코드를 실행하기 위해 다음 두 가지 엔진을 사용합니다:
- 인터프리터: 한 줄씩 해석하며 실행 -> 빠르게 시작되나 느림
- JIT(Just-In-Time) 컴파일러: 반복 실행되는 코드를 네이티브 코드로 변환 -> 성능 최적화
- 즉, 자바는 초기에 느릴 수 있지만 실행 중 JIT가 성능을 점차 개선합니다.
최종 실행 흐름 요약
1. HelloWorld.java (작성)
↓
2. javac 컴파일
↓
3. HelloWorld.class (바이트코드 생성)
↓
4. JVM 시작
└─ ClassLoader → MethodArea/Heap/Stack에 클래스 배치
└─ Execution Engine → 바이트코드 해석/실행
↓
5. 출력: Hello, world!
실무에서 중요하게 느낀 점
- 클래스 로딩 순서나 JVM 메모리 구조를 이해하면, static 초기화, 메모리 누수, GC 최적화 같은 이슈를 더 쉽게 해결할 수 있었습니다.
- 특히 Spring, Hibernate 등은 내부적으로 ClassLoader와 Reflection을 활용하므로 JVM의 이 구조가 매우 중요하게 작용합니다.
함께 보면 좋은 글:
'프로그래밍 > Java' 카테고리의 다른 글
💻 [Java] Lombok vs record 실무 비교 — DTO/VO에 무엇을 쓸까? (4) | 2025.08.11 |
---|---|
💻 [Java] Record 완전 정복 — 불변 데이터 클래스를 위한 언어 기능 (3) | 2025.08.11 |
💻 [Java] 단일 vs 멀티 스레드, 직접 실험해본 성능 차이 (2) | 2025.07.01 |
💻 [Java & Spring] 비동기 처리와 스레드, 언제 쓰고 어떻게 써야 할까? (0) | 2025.07.01 |
📌 [Java] Virtual Thread vs 일반 Thread – 직접 비교해봤습니다 (1) | 2025.04.16 |