JVM 핵심 정리: 자바 성능 최적화의 첫걸음
자바 애플리케이션의 성능은 JVM(Java Virtual Machine) 에 대한 깊은 이해로부터 시작됩니다. 이번 글에서는 JVM의 기본 구조부터 클래스 로딩, 컴파일러, 그리고 최적화 기법까지 핵심 내용을 정리해봅니다.
1. JVM 기본 구조: 스택 기반 머신과 클래스 로더
JVM은 스택 기반 머신으로, 모든 연산은 스택의 최상단 값을 기준으로 이루어집니다. JVM의 가장 중요한 역할 중 하나는 클래스를 메모리로 불러오는 클래스 로더입니다. 자바 9 이후 JDK는 다음과 같은 계층 구조의 클래스 로더를 제공합니다.
- 부트스트랩 클래스 로더 (Bootstrap Class Loader): JVM 내부에 내장된 네이티브 로더입니다.
java.base
와 같은 핵심 모듈의 클래스를 로드합니다. API 상에서는 부모가null
로 보입니다. - 플랫폼 클래스 로더 (Platform Class Loader): 자바 9에서 변경된 로더로, 표준 Java SE 및 일부 플랫폼 모듈을 로드합니다.
ClassLoader.getPlatformClassLoader()
로 접근할 수 있습니다. - 애플리케이션/시스템 클래스 로더 (Application/System Class Loader): 우리가 작성한 애플리케이션 코드(
CLASSPATH
또는 모듈패스)를 로드합니다.ClassLoader.getSystemClassLoader()
로 접근 가능하며, 공식 가이드라인은 이 로더의 내부 구현에 의존하지 말 것을 권장합니다.
2. 클래스 파일 구조 (Class File Format)
JVM이 로드하는 .class
파일은 단순히 코드를 담고 있는 파일이 아니라, 정해진 형식을 가진 데이터 구조입니다.
- 매직 넘버 (Magic Number): 클래스 파일의 시작은 항상
0xCAFEBABE
입니다. 이를 통해 JVM이 파일 형식을 즉시 식별합니다. - 버전 정보 (Version): 파일이 어떤 버전의 자바로 컴파일되었는지 명시합니다. 예를 들어, 메이저 버전
52
는 자바 8로 컴파일되었음을 의미합니다. 만약 컴파일된 버전보다 낮은 JRE에서 실행하면UnsupportedClassVersionError
가 발생합니다. - 상수 풀 (Constant Pool): 클래스에서 사용되는 모든 심볼 정보(문자열, 클래스 이름, 메서드 이름 등)가 저장되는 가장 크고 복잡한 부분입니다.
- 접근 플래그 (Access Flags): 클래스의 속성(예:
public
,final
,abstract
)을 나타내는 플래그 값입니다. - 필드 및 메서드 정보: 클래스 멤버 변수와 메서드에 대한 상세 정보를 담습니다. 특히 메서드 정보에는 실제 실행될 바이트코드가 포함되어 있습니다.
- 속성 정보 (Attributes): 클래스, 필드, 메서드에 추가적인 메타데이터를 제공합니다.
- Code: 메서드의 실제 바이트코드를 담고 있습니다.
- LineNumberTable: 바이트코드와 원본 소스코드의 줄 번호를 매핑하여 디버깅에 활용됩니다.
3. JIT와 AOT: 바이트코드를 네이티브 코드로
JVM은 JIT (Just-In-Time) 컴파일러와 AOT (Ahead-Of-Time) 컴파일러를 통해 바이트코드를 네이티브 코드로 변환합니다.
JIT (Just-In-Time) 컴파일러
- 언제: 프로그램 실행 중 자주 실행되는 "핫 스팟" 메서드를 동적으로 컴파일합니다.
- 어떻게: 처음에는 바이트코드를 인터프리터로 실행하다가, 특정 횟수 이상 호출된 메서드를 JIT이 네이티브 코드로 변환 후 코드 캐시에 저장합니다. 이후부터는 캐싱된 코드를 직접 실행합니다.
- 장점: 런타임 프로파일링을 기반으로 메서드 인라이닝 같은 강력한 최적화를 수행하여 높은 성능을 냅니다.
- 단점: 초기 실행 속도가 느립니다(웜업 시간).
AOT (Ahead-Of-Time) 컴파일러
- 언제: 프로그램 실행 전에 미리 바이트코드를 네이티브 코드로 변환합니다.
- 어떻게: 빌드 단계에서 전체 클래스 또는 일부를 네이티브 실행 파일 형태로 미리 컴파일합니다.
- 장점: 빠른 시작 속도가 필요한 서버리스 환경에 유리하며, JIT 컴파일 오버헤드가 없습니다.
- 단점: 런타임 정보가 없어 JIT보다 최적화 성능이 떨어지며, 특정 플랫폼에 종속적입니다.
4. JIT 컴파일러의 주요 최적화 기법
JIT는 런타임 정보를 활용하여 다양한 최적화를 수행합니다.
1. 동적 인라이닝 (Dynamic Inlining)
메서드 호출 코드를 직접 호출 위치에 삽입하여 호출 오버헤드를 제거하고 추가 최적화 기회를 얻는 기법입니다.
예시:
class MathUtil {
int square(int x) { return x * x; }
}
// JIT 컴파일 전
int result = mathUtil.square(5);
// JIT 컴파일 후 (인라이닝)
int result = 5 * 5;
2. 가상 호출 최적화 (Virtual Call Optimization)
자바의 기본인 가상 호출(Virtual Call) 은 런타임에 실제 객체 타입을 확인해야 해서 비용이 발생합니다. JIT는 이를 최적화합니다.
예시:
// JIT 컴파일 전: 가상 호출(동적 디스패치)
Shape s = new Circle();
s.draw(); // 런타임에 s가 Circle 타입임을 확인 후 draw() 호출
JIT는 프로파일링을 통해 s.draw()
호출 지점에서 항상 Circle
객체만 넘어온다는 것을 파악하면, 가상 호출 대신 Circle.draw()
를 직접 호출하는 코드로 비가상화(Devirtualization) 합니다.
5. JVM 모니터링 툴
JVM의 상태를 진단하고 분석하는 데 사용되는 도구는 여러 계층으로 나뉩니다.
- JMX (Java Management Extensions): 자바 레벨의 관리/모니터링 기술입니다.
jconsole
이나VisualVM
을 통해 힙 사용량, GC 정보, 스레드 상태 등을 실시간으로 시각화하여 볼 수 있습니다. - Java Agent:
Instrumentation API
를 사용하여 런타임에 클래스 바이트코드를 변환하고 계측합니다. APM(Application Performance Management) 도구들이 메서드 호출 시간을 추적하는 데 주로 사용합니다. - JVMTI (JVM Tool Interface): 네이티브(C/C++) 레벨에서 JVM 이벤트(스레드 시작, GC 실행 등)에 후킹하여 디버깅이나 프로파일링을 수행합니다.
- SA (Serviceability Agent): JVM 크래시나 힙 덤프를 분석하는 데 특화된 기술입니다.
jhsdb
같은 툴을 사용하여 크래시 덤프나 라이브 JVM의 내부 상태를 읽고 문제 원인을 추적합니다.
'📗Java' 카테고리의 다른 글
[자바최적화] 가비지 컬렉션 이해하기 (2) | 2025.08.17 |
---|---|
[자바 최적화] 최적화와 성능 정의 (1) | 2025.08.10 |
[Java] static class 와 static method 이해하기 (3) | 2024.09.21 |
[Java] Virtual Thread 알아보기 (0) | 2024.05.23 |
[Java] Arrays.sort() 어떤 정렬 알고리즘을 쓸까? (2) | 2024.04.30 |