티스토리 뷰
Java 실행 과정을 살펴보려면, 자바 가상 머신 JVM에 대한 이해가 필요하다.
JVM은 자바 어플리케이션의 실행을 가능하게 하는 핵심 기술으로, 성능 최적화에 대해 고민한다면 빠질 수 없는 내용이기 때문이다.
이번 글은 1. JVM 구성 요소와 Warm-up 전략에 대해서 작성한다.
1. JVM 구성 요소와 JVM Warm-up 전략
2. JVM 메모리 구조
3. JVM 가비지 컬렉션
4. 자바 어플리케이션 실행 과정
5. Java 비동기 처리 (프로세스, 쓰레드, 비동기 처리)
1. JVM 구성 요소
JVM은 1. 클래스 로더, 2. 실행 엔진, 3. 런타임 데이터 영역 세 가지 구성 요소로 나뉜다.
1.1. 클래스 로더(Class Loader)
클래스 로더는 자바 어플리케이션 실행 시 .class
클래스 파일을 읽고, 메모리에 적재해서 JVM에서 사용 가능한 형태로 동적으로 로드한다.
1. 클래스 파일(.class)을 동적으로 로드
JVM 메모리인 런타임 데이터 영역(Runtime Data Area)의 메서드 영역에 클래스 파일을 올린다.
2. 로드된 클래스 파일 링크 (Linking)
(1) 검증(Verification): 클래스 파일이 JVM 규격에 맞는지 검증한다.
(2) 준비(Preparation): 클래스의 정적 변수(static)와 기본값이 초기화된다.
(3) 해결(Resolution): 상수 풀(Constant Pool)에 포함된 심볼릭 참조를 실제 메모리 주소로 변경한다.
3. 클래스 파일 초기화
Static 필드 및 블록을 초기화한다.
💡 클래스 로더의 지연 로딩
클래스 로더는 필요할 때만 클래스를 로드하는 지연 로딩(Lazy Loading) 방식을 사용한다.
지연 로딩(Lazy Loading) 방식을 사용하는 이유는 어플리케이션을 실행하면 모든 클래스가 실행 중에 호출되는 건 아닐 수 있다.
따라서 JVM 은 미리 로드하지 않고 접근되는 순간에만 클래스 파일을 찾아 메모리에 적재해서 메모리 사용량을 줄였다.
이를 통해 JVM 초기 실행 속도도 빨라지는 효과가 있다.
하지만 자바 애플리케이션을 실행한 직후, 클래스 로딩 과정이 완료되지 않기 때문에 초기 Latency 발생을 유발한다.
1.2. 실행 엔진(Execution Engine)
👩💻 자바 언어에서 개발자가 작성한 자바 소스 파일 .java
은 사람이 읽을 수 있는 언어로 작성된다.
💻 그리고 자바 컴파일러(javac
)는 .java
파일을 바이트코드로 변환하여 .class
파일을 생성한다.
📄 바이트코드는 기계어로 직접 실행되지 않고, 실행 엔진에 의해 실행된다.
💡 Write Once, Run Anywhere
자바는 "한 번 작성하면 어디서든 실행된다"는 철학을 바탕으로 설계되었다.
바이트코드는 JVM이 이해할 수 있는 중간 언어인데, 여기서 자바의 Write Once, Run Anywhere 철학을 알 수 있다.
자바는 운영 체제에서 독립적으로 돌아가는 코드를 원했다.
JRE(Java Runtime Environment, 자바 실행 환경)를 컴퓨터에 셋팅하면 내부에 JVM이 설치되어 있고,
운영체제에 구애받지 않고 자바 컴파일러 만으로도 실행할 수 있게 되었다.
"자바 파일 → 바이트코드 → 기계어 변환" 이 구조로 플랫폼에서 독립적인 언어로 다양한 운영 체제에서 동일하게 동작하고, 기계어처럼 특정 OS에 종속되지 않게 된 것이다.
실행 엔진 구성요소
실행 엔진은 두 가지 방식으로 바이트코드를 기계어로 변환한다.
1. 인터프리터(Interpreter)
바이트코드를 한 줄씩 기계어로 변환해서 실행한다.
초기 실행 속도는 빠르지만, 매번 해석해야하기 때문에 반복되는 코드에서는 비효율적이다.
2. JIT(Just-In-Time) 컴파일러
인터프리터의 단점을 보완해서, 자주 실행되는 코드(핫스팟)를 기계어로 변환한다.
변환된 기계어는 캐시에 저장되며, 동일한 코드가 반복 실행될 때 재사용되어 성능이 향상된다.
1.3. 런타임 데이터 영역(Runtime Data Area)
JVM 메모리 구조는 내용이 많은 관계로 다음 글에서 자세히 다루도록 할 예정이다.
1. Heap: 객체와 인스턴스를 저장한다. GC(가비지 컬렉션)의 주요 대상이다.
2. Method Area: 클래스 메타데이터와 상수 풀(Constant Pool)을 저장한다.
3. Stack: 메서드 호출, 로컬 변수, 리턴 주소를 저장한다.
4. PC Register: 현재 실행 중인 JVM 명령의 주소를 저장한다.
5. Native Method Stack: 네이티브(C/C++) 메서드 호출 시 사용한다.
2. 자바 기반 웹 애플리케이션 배포 시 초기 Latency 발생
자바 기반의 웹 애플리케이션을 배포한 직후, 초기 요청 시 지연(Latency)이 발생하는 경우가 많다.
주요 원인은 1. 클래스 로더의 지연로딩, 2. JIT 컴파일 두개를 들 수 있다.
2.1. 원인 1: 클래스 로더의 지연 로딩(Lazy Loading)
클래스 로더는 어플리케이션의 모든 클래스를 한 번에 로드하지 않고, 클래스가 필요한 순간에 로드한다.
이는 지연 로딩(Lazy Loading) 방식이라고 하는데, 특정 기능이 처음 호출될 때 다음과 같은 추가 작업이 수행해야 한다.
1. 클래스 파일 탐색 및 읽기
2. JVM 메모리에 로드
3. 링크 및 초기화
이 작업들은 추가적인 CPU 및 I/O 자원을 소모하기 때문에
클래스가 많거나 동적으로 로드되는 클래스가 많은 경우 초기 요청에서 Latency 유발한다.
그런데 Spring은 어노테이션 기반 프레임워크로 빈(Bean)을 스캔하고 초기화한다.
@ComponentScan
이나 @SpringBootApplication
을 사용하는 경우, 클래스 로더는 특정 패키지 내 모든 클래스를 탐색하고 Reflection을 사용해 애노테이션 정보를 확인하기 때문에, 이러한 동적 로딩은 초기화 시간이 오래 걸리는 주요 원인이 된다.
2.2. 원인 2: JIT 컴파일
JVM은 인터프리터를 사용해 코드를 실행한 뒤, 자주 실행되는 핫스팟 코드를 JIT 컴파일러로 컴파일한다.
JIT 컴파일은 컴파일된 코드의 실행 속도를 향상시키지만, 단순히 바이트코드를 기계어로 변환하는 것에 그치지 않고 실행 성능을 극대화하기 위한 최적화 작업을 수행하기 때문에 초기 컴파일 단계에서 시간이 오래 소요될 수 있다.
3. JVM Warm-up 전략
Warm-up은 JVM이 최적화된 상태로 동작하도록 돕는 과정이다.
이를 통해 자바 기반 웹 애플리케이션 배포 시 발생할 수 있는 초기 Latency를 줄일 수 있다.
3.1. 전략 1: 프리로딩 및 초기화 작업
어플리케이션 시작 시 필요한 주요 클래스는 미리 로드한다.
Spring에서는
@PostConstruct
를 사용해 초기화 작업을 수행하거나,
빈(Bean
)을 조기에 로드하거나,
어플리케이션 시작 시 특정 엔드포인트를 호출해 초기화를 수행할 수 있다.
3.2. 전략 2: Pre-JIT
어플리케이션이 시작되면 미리 자주 사용되는 코드 경로를 강제로 호출하여,
해당 코드들이 JIT 컴파일러에 의해 미리 컴파일되도록 유도한다.
이를 통해 첫 번째 요청이 들어오기 전에 JIT 작업이 완료되어 초기 Latency를 줄일 수 있다.
3.3. 전략 3: AOT(Ahead-Of-Time) 컴파일
AOT는 어플리케이션이 시작될 때 이미 컴파일이 완료되어 초기 지연을 최소화할 수 있다.
특히, GraalVM의 Native Image는 어플리케이션을 네이티브 바이너리로 컴파일하여 JVM 런타임 없이 실행할 수 있다.
단, AOT 컴파일은 빌드 시간이 길고 런타임 메모리 최적화가 부족할 수도 있다.
다음 글에서는 JVM 메모리 구조에 대해 다뤄보겠다.
'language > java' 카테고리의 다른 글
[JVM 시리즈] JVM 가비지 컬렉션 (1) | 2024.11.20 |
---|---|
[JVM 시리즈] 자바 어플리케이션 메모리 구조 (2) | 2024.11.19 |
Collection 객체의 복사 방법 (0) | 2024.11.17 |
얕은 복사, 깊은 복사, 방어적 복사 (0) | 2024.11.16 |
DTO 클래스 vs Record 어떤 경우에 사용할까? (1) | 2024.11.15 |
- Total
- Today
- Yesterday
- Java
- jvm warm-up 전략
- array
- addFilterBefore
- junit5
- redisson 분산락
- QueryDSL
- 스프링오류
- checkout
- n+1
- 추상클래스
- 스프링 스케줄링
- 오블완
- 자바 어플리케이션 실행 과정
- dto 클래스 생성자
- ChatGPT
- Git
- 티스토리챌린지
- port
- JPA
- MultipleBagFetchException
- Spring Security
- bucket4j
- spring boot 3
- Kotlin
- Linux
- FetchJoin
- MongoDB
- 배열
- Cannot construct instance of
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |