JVM 구조

이미지 출처: jvm-java-virtual-machine
클래스 로더 (Classloader)
클래스 로더는 JVM의 서브시스템으로서 클래스 파일을 로드하는 데 사용된다. 자바 프로그램을 실행할 때마다 가장 먼저 클래스 로더에 의해 프로그램이 로드된다.
Java 9 이전
- 부트스트랩 클래스 로더 (Bootstrap ClassLoader): 이는 첫 번째 클래스 로더로서, 익스텐션 클래스 로더의 상위 클래스입니다.
java.lang, java.net, java.util, java.io, java.sql 등의 자바 표준 에디션 클래스 파일을 포함하는 rt.jar 파일을 로드한다.
- 익스텐션 클래스 로더 (Extension ClassLoader): 부트스트랩 클래스 로더의 자식이자 시스템 클래스 로더의 부모 클래스 로더이다.
$JAVA_HOME/jre/lib/ext 디렉토리에 위치한 JAR 파일들을 로드한다.
- 시스템/애플리케이션 클래스 로더 (System/Application ClassLoader): 익스텐션 클래스 로더의 자식 클래스 로더이다. 클래스패스에서 클래스 파일들을 로드한다. 기본적으로 클래스패스는 현재 디렉토리로 설정되어 있다.
-cp 또는 -classpath 스위치를 사용하여 클래스패스를 변경할 수 있다. 애플리케이션 클래스 로더라고도 알려져 있다.
Java 9 이후
- 부트스트랩 클래스 로더 (Bootstrap ClassLoader):
java.base 모듈만 로드하며 java.base에는 java.lang, java.util 등의 핵심 클래스가 포함된다.
- 플랫폼 클래스 로더 (Platform ClassLoader): Java 9에서 새롭게 도입된 클래스 로더이며
java.sql, java.xml, java.logging 등 표준 플랫폼 모듈을 로드한다. ClassLoader.getPlatformClassLoader()를 통해 접근할 수 있다.
- 애플리케이션 클래스 로더 (Application ClassLoader): 애플리케이션 모듈 및 클래스패스에 있는 클래스를 로드한다.
ClassLoader.getSystemClassLoader()를 통해 접근할 수 있다.
info: Java 9부터는 모듈 시스템의 도입으로 익스텐션 클래스 로더가 더 이상 사용되지 않습니다.
public class ClassLoaderExample {
public static void main(String[] args) {
Class<?> c = ClassLoaderExample.class;
System.out.println(c.getClassLoader());
System.out.println(String.class.getClassLoader());
}
}
jdk.internal.loader.ClassLoaders$AppClassLoader@5bc2b487
null

이미지 출처: memory-management-in-java
JVM 메모리 구조
Java 프로그램은 실행 시 Java Virtual Machine (JVM) 위에서 동작하며, JVM은 메모리를 다음과 같이 구분된다.
- 메서드 영역(Method Area):
클래스 로더에 의해 로드된 클래스 메타데이터(필드, 메서드, 인터페이스 등)가 저장된다.
런타임 상수 풀도 이 영역에 포함되며, 상수 및 리터럴을 저장한다.
모든 스레드가 공유하는 영역이다.
- 힙 영역(Heap Area):
객체와 배열이 동적으로 할당되는 영역이다.
가비지 컬렉션의 대상이 된다.
모든 스레드가 공유하는 영역이다.
- 스택 영역(Stack Area):
각 스레드마다 생성되며, 메서드 호출 시마다 스택 프레임이 추가된다.
지역 변수, 매개변수, 연산 중간 결과 등이 저장된다.
- PC 레지스터(Program Counter Register):
각 스레드가 실행할 JVM 명령의 주소를 저장한다.
- 네이티브 메서드 스택(Native Method Stack):
Java 외의 네이티브 코드를 실행하기 위한 스택입니다.
기본 자료형(Primitive Types)과 메모리 할당
Java의 기본 자료형은 스택 영역에 직접 저장되며, 총 8가지이다.
| 자료형 | 크기 | 범위 | 비트 패턴 |
|---|
| byte | 8비트 | -128 ~ 127 | 2의 보수 |
| short | 16비트 | -32,768 ~ 32,767 | 2의 보수 |
| int | 32비트 | -231 ~ 231 - 1 | 2의 보수 |
| long | 64비트 | -263 ~ 263 - 1 | 2의 보수 |
| float | 32비트 | ±1.4E-45 ~ ±3.4028235E38 | IEEE 754 |
| double | 64비트 | ±4.9E-324 ~ ±1.7976931348623157E308 | IEEE 754 |
| char | 16비트 | '\u0000' (0) ~ '\uffff' (65,535) | 유니코드 |
| boolean | JVM에 따라 다름 | true 또는 false | 1비트 또는 1바이트 |
메모리 저장 방식 상세 분석
- 정수형 (byte, short, int, long)
- 2의 보수법으로 부호를 표현한다.
- 오버플로 및 언더플로 발생 시 값이 순환한다.
- 실수형 (float, double)
- IEEE 754 부동소수점 표준을 따른다.
- float: 1비트 부호, 8비트 지수, 23비트 가수.
- double: 1비트 부호, 11비트 지수, 52비트 가수.
- 문자형 (char)
- UTF-16 인코딩을 사용하여 유니코드 문자 표현.
- 서플리먼트리 문자(2바이트를 초과하는 문자)는 두 개의 char로 표현된다.
- 불리언형 (boolean)
- JVM 구현에 따라 1비트 또는 1바이트로 저장된다.
- 배열에서는 1바이트를 사용하여 주소 계산을 용이하게 한다.
참조 자료형(Reference Types)과 메모리 할당
참조 자료형은 힙 영역에 객체를 생성하고, 스택 영역에는 그 객체의 참조값(주소) 을 저장한다.
- 클래스 타입
- 사용자 정의 객체로, 필드와 메서드를 포함합니다.
- 예: MyClass obj = new MyClass();
- 배열 타입
- 동일한 타입의 변수들을 연속적으로 저장.
- 배열 자체는 객체로 취급되며, 힙에 저장됩니다.
- 인터페이스 및 열거형
- 구현 클래스나 열거형 인스턴스가 힙에 저장됩니다.
객체의 메모리 구조
- 객체 헤더
- Mark Word: 객체의 동기화 정보, 해시 코드 등을 저장.
- 클래스 포인터: 해당 객체의 클래스 메타데이터를 가리킴.
- 인스턴스 변수
- 패딩(Padding)
스택 프레임(Stack Frame) 상세 구조
각 메서드 호출 시 생성되는 스택 프레임은 다음과 같은 구조를 가진다.
- 로컬 변수 배열(Local Variable Array)
- 메서드의 매개변수와 지역 변수가 저장된다.
- 인덱스로 접근하며, int는 1 슬롯, long과 double은 2 슬롯을 사용한다.
- 오퍼랜드 스택(Operand Stack)
- 연산의 중간 결과를 저장하는 스택 구조.
- 바이트코드 명령어에 의해 PUSH와 POP이 이루어진다.
- 프레임 데이터(Frame Data)
- 동적 링크(Dynamic Link): 호출한 메서드의 스택 프레임을 가리키는 포인터.
- 메서드 반환 주소: 메서드 종료 시 복귀할 주소를 저장.
가비지 컬렉션(Garbage Collection) 메커니즘
객체의 생존 기간과 세대 구분
- Young Generation
- 새롭게 생성된 객체가 할당된다.
- Eden Space와 두 개의 Survivor Space (S0, S1) 로 구성.
- Minor GC가 주로 수행된다.
- Old Generation
- Young Generation에서 생존한 장수 객체가 이동된다.
- Major GC 또는 Full GC가 수행된다.
가비지 컬렉션 알고리즘
- Mark-and-Sweep
- 마크 단계: 도달 가능한 객체를 식별하고 마크한다.
- 스위프 단계: 마크되지 않은 객체를 회수한다.
- Mark-and-Compact
- 컴팩션 단계를 추가하여 메모리 단편화를 줄인다.
- Copying
- Young Generation에서 사용되며, 살아있는 객체를 다른 공간으로 복사하고 기존 공간을 정리한다.
루트 집합(Root Set)
- GC Roots: 도달 가능한 객체의 시작점으로, 다음을 포함한다:
- 스택 프레임의 로컬 변수
- 정적 필드
- JNI 참조
메모리 최적화 기법
객체 풀링(Object Pooling)
- 빈번한 객체 생성과 소멸을 줄이기 위해 재사용 가능한 객체 풀을 사용한다.
- 예: Integer.valueOf(int i)는 -128에서 127 사이의 값을 캐시한다.
불변 객체(Immutable Object) 사용
- 불변 객체는 상태 변경이 불가능하므로, 스레드 안전성과 캐싱 효율성이 높아진다.
- 예: String, Integer (자동 박싱된 값).
메모리 누수 방지
- 컬렉션에서 사용하지 않는 객체를 제거한다.
- 이벤트 리스너나 콜백에서의 약한 참조(Weak Reference) 사용을 고려한다.
상수 풀(Constant Pool)과 문자열 관리
런타임 상수 풀(Runtime Constant Pool)
- 클래스 파일에 있는 상수 풀을 로드하여 메서드 영역에 저장한다.
- 동적 상수는 String.intern() 메서드를 통해 상수 풀에 추가할 수 있다.
문자열 상수 풀(String Constant Pool)
- 동일한 문자열 리터럴은 상수 풀에서 공유된다.
- new String("text")는 힙에 새로운 객체를 생성하므로 주의가 필요하다.
메모리 모델과 스레드 안전성
Java 메모리 모델(Java Memory Model, JMM)
- 변수의 가시성(Visibility) 과 재배열(Reordering) 을 규정한다.
- volatile 키워드: 변수의 변경이 즉시 다른 스레드에 가시적으로 반영되도록 한다.
- happens-before 관계: 메모리 간섭을 방지하기 위한 실행 순서 보장.
동기화(Synchronization)
- 모니터 락(Monitor Lock) 을 통해 객체에 대한 동기화를 수행한다.
- synchronized 키워드: 메서드나 블록에 적용하여 원자성을 보장한다.
전문적인 메모리 디버깅과 튜닝
메모리 프로파일링 도구
- JVisualVM, Eclipse Memory Analyzer 등을 사용하여 힙 덤프 분석.
- JConsole, JProfiler로 실시간 메모리 사용량 모니터링.
GC 로그 분석
- JVM 옵션 -Xlog:gc*를 사용하여 가비지 컬렉션 로그를 수집.
- GC 동작을 분석하여 메모리 누수나 성능 병목 현상을 파악.
JVM 옵션 조정
- 힙 크기 조절: -Xms(초기 힙 크기), -Xmx(최대 힙 크기).
- GC 알고리즘 선택: -XX:+UseG1GC, -XX:+UseConcMarkSweepGC 등.