자바 가상 머신 (JVM)
한눈에 보기
JVM은 바이트코드를 로딩하고 검증한 뒤 실행하는 가상 컴퓨터로 클래스 로더, 런타임 데이터 영역, 실행 엔진으로 구성된다. 힙과 스택과 메타스페이스, JIT와 가비지 컬렉션을 정리한다.
최종 업데이트 2026.09.17
자바 가상 머신(JVM, Java Virtual Machine)은 컴파일된 바이트코드(bytecode)를 로딩하고 검증한 뒤 실행하는 가상의 컴퓨터로, 자바 프로그램과 운영체제 사이에서 실행 환경을 제공한다. 클래스 로더(Class Loader), 런타임 데이터 영역(Runtime Data Area), 실행 엔진(Execution Engine)의 세 축으로 구성되며, 메모리 관리와 가비지 컬렉션(Garbage Collection)까지 책임진다. 한 번 작성하면 어디서나 실행된다는 자바의 이식성은 각 운영체제에 맞게 구현된 JVM이 동일한 명세를 따르기 때문에 성립한다.
JVM, JRE, JDK의 관계
JVM은 바이트코드를 실행하는 엔진이고, JRE(Java Runtime Environment)는 JVM에 표준 라이브러리를 더한 실행 환경이며, JDK(Java Development Kit)는 JRE에 javac 컴파일러와 javap, jcmd 같은 개발 도구를 더한 개발 키트다. Java 11부터는 JRE가 별도 배포되지 않고 JDK로 통합되었으며, 필요하면 jlink로 맞춤 런타임을 만드는 방식이 권장된다. 오라클의 핫스팟(HotSpot)이 가장 널리 쓰이는 JVM 구현체이고, 이 문서의 세부 동작도 핫스팟 기준이다.
클래스 로더 서브시스템
클래스 로더는 .class 파일을 실행 중에 필요해지는 시점에 메모리로 가져온다. 과정은 로딩(loading), 링킹(linking), 초기화(initialization)로 나뉘며, 링킹은 다시 바이트코드의 유효성을 확인하는 검증, static 필드를 기본값으로 잡아 두는 준비, 심벌릭 참조를 실제 참조로 바꾸는 해석으로 나뉜다. 로더는 핵심 모듈을 담당하는 부트스트랩 로더, 플랫폼 로더, 애플리케이션 로더가 계층을 이루고, 요청을 먼저 부모에게 위임하는 부모 위임 모델(parent delegation model)로 동작해 java.lang.String 같은 핵심 클래스가 임의 코드로 바꿔치기되는 것을 막는다.
런타임 데이터 영역
JVM 메모리는 모든 스레드가 공유하는 영역과 스레드마다 따로 갖는 영역으로 나뉜다. 공유 영역의 중심은 힙(heap)으로, new로 만든 모든 객체와 배열이 여기 할당되고 가비지 컬렉션의 대상이 된다. 클래스 구조, 메서드 바이트코드, static 변수, 상수 풀을 담는 메서드 영역(method area)은 명세상의 개념이고, 핫스팟에서는 Java 8부터 네이티브 메모리를 쓰는 메타스페이스(Metaspace)로 구현된다. 그 이전의 고정 크기 영구 세대(PermGen)는 메모리 부족 문제로 제거되었다.
스레드별 영역으로는 현재 실행 중인 바이트코드 주소를 가리키는 PC 레지스터(Program Counter Register), 메서드 호출마다 프레임이 쌓이는 JVM 스택, JNI 호출을 위한 네이티브 메서드 스택이 있다. 각 스택 프레임은 지역 변수 배열과 피연산자 스택(operand stack)을 담는데, 바이트코드는 피연산자 스택에 값을 올리고 내리며 계산하는 스택 머신 방식으로 실행된다. 재귀가 너무 깊어 스택이 넘치면 StackOverflowError, 힙이나 메타스페이스가 부족하면 OutOfMemoryError가 발생한다.
| 영역 | 공유 여부 | 저장 내용 | 관련 오류 |
|---|---|---|---|
| 힙 | 전 스레드 공유 | 객체와 배열 인스턴스 | OutOfMemoryError: Java heap space |
| 메서드 영역 (메타스페이스) | 전 스레드 공유 | 클래스 메타데이터, static 변수, 상수 풀 | OutOfMemoryError: Metaspace |
| JVM 스택 | 스레드별 | 프레임, 지역 변수, 피연산자 스택 | StackOverflowError |
| PC 레지스터 | 스레드별 | 현재 명령 주소 | 없음 |
| 네이티브 메서드 스택 | 스레드별 | 네이티브 코드 호출 정보 | StackOverflowError |
실행 엔진과 가비지 컬렉션
실행 엔진은 바이트코드를 인터프리터로 해석 실행하다가, 자주 실행되는 핫스팟 코드를 JIT 컴파일러(Just-In-Time Compiler)가 네이티브 기계어로 컴파일해 성능을 끌어올린다. 빠른 C1과 최적화가 강한 C2를 조합하는 계층형 컴파일이 기본이며, 프로파일 기반으로 인라이닝과 탈출 분석 같은 최적화를 수행한다.
가비지 컬렉터는 참조가 닿지 않는 객체를 자동 회수한다. 대부분의 객체는 금방 죽는다는 세대 가설에 따라 힙을 젊은 세대(young generation)와 늙은 세대(old generation)로 나누고, 젊은 세대의 에덴(Eden)에서 할당된 객체가 마이너 GC에서 살아남기를 거듭하면 늙은 세대로 승격된다. Java 9부터 기본 컬렉터인 G1(Garbage First)은 힙을 리전(region) 단위로 나눠 중단 시간 목표를 지키며 수집하고, Java 21에는 세대별 ZGC가 추가되어 대용량 힙에서도 밀리초 이하의 중단 시간을 노릴 수 있다.
public class MemoryDemo {
private static final int LIMIT = 100; // 상수: 메서드 영역 관련
private int count; // 인스턴스 필드: 힙의 객체 안
public static void main(String[] args) { // 프레임이 스택에 쌓인다
int local = 10; // 지역 변수: 스택 프레임
MemoryDemo demo = new MemoryDemo(); // 객체는 힙, 참조는 스택
String name = "devfit"; // 리터럴은 힙의 문자열 풀
}
}
면접에서 이렇게 물어본다
- JVM의 전체 구조를 클래스 로더, 런타임 데이터 영역, 실행 엔진으로 나눠 설명해 보라.
- 힙과 스택에 각각 무엇이 저장되며, 지역 변수와 객체와 static 변수는 어디에 위치하는가.
- PermGen이 사라지고 메타스페이스가 도입된 배경을 설명해 보라.
- 인터프리터와 JIT 컴파일러가 함께 쓰이는 이유는 무엇인가.
- 세대별 가비지 컬렉션의 동작 원리와 G1의 특징을 설명해 보라.
Java에서 같이 읽기
- 오토 박싱과 오토 언박싱 (Auto Boxing and Unboxing)오토 박싱은 기본형 값을 래퍼 클래스 객체로, 오토 언박싱은 그 반대로 자동 변환하는 기능이다. Integer 캐시, == 비교 함정, NullPointerException 위험과 성능 비용을 JVM 메모리 관점에서 정리한다.
- Call by Value와 Call by Reference자바는 기본형이든 참조형이든 언제나 값에 의한 호출만 사용하며, 참조형은 객체가 아니라 참조값이 복사되어 전달된다. swap 실패 예제와 스택 프레임 관점으로 그 근거를 설명한다.
- 캐스팅 (Casting), 업캐스팅과 다운캐스팅캐스팅은 값이나 참조를 다른 타입으로 취급하는 변환이다. 기본형 확대와 축소 변환, 업캐스팅과 다형성, 다운캐스팅과 instanceof 패턴 매칭, ClassCastException 예방법을 다룬다.
- 자바 컴파일 과정 (Java Compile Process)javac가 소스를 바이트코드로 바꾸고 JVM이 로딩, 링킹, 초기화 후 인터프리터와 JIT로 실행하는 전체 흐름을 정리한다. 클래스 파일 구조와 부모 위임 모델, 계층형 컴파일까지 다룬다.
이 개념, 내 코드에서는 어떻게 물어볼까요
데브핏(DevFit)에 채용공고 URL을 붙여 넣고 레포지토리를 연결하면, 내가 쓴 커밋에서 예상 질문과 어필 포인트를 만듭니다. 하루 10회까지 무료입니다. 합불 판정이나 지원자 랭킹은 만들지 않고, 최종 판단은 사람이 합니다.
공고 넣고 시작하기