콘텐츠로 바로가기

Virtual Machines & JIT Compilation

Virtual Machines 및 JIT Compilation의 정의, 범위, 선행 지식, 학습 주제, 참고 근거를 정리한 CS&E 학습 노드입니다.

Article
M

Me

hyunyoun's Blog

programming-languages-compilersprogramming-languagescompilerscompiler-designimplementationvirtual-machinesjit-compilationlanguages-compilers9 min read

1. Overview

가상 머신과 JIT 컴파일(Virtual Machines & JIT Compilation)은 소스 코드를 특정 CPU 아키텍처에 종속되지 않는 **바이트코드(Bytecode)**로 컴파일하고, 이를 런타임에 실행하는 **가상 머신(VM)**의 인터프리터 엔진과, 자주 실행되는 핫스팟(Hot Spot) 코드를 런타임에 네이티브 기계어로 변환하는 JIT(Just-In-Time) 컴파일러의 내부 구조를 다룹니다.

학습자는 JVM의 스택 기반 바이트코드(javac.class 파일), CPython의 바이트코드(dis 모듈), V8의 Ignition 인터프리터 + TurboFan JIT 파이프라인을 비교합니다. 이어서 JIT의 핵심 도전 과제인 Tiered Compilation(인터프리터 → C1 JIT → C2 JIT), 탈최적화(Deoptimization, Speculation Failure), **인라인 캐시(Inline Cache)**의 동작 원리를 살펴보고, WebAssembly(Wasm)의 플랫폼 독립적 VM 전략까지 정리합니다.

2. Scope & Boundaries

In-Scope

  • 가상 머신 구조 (VM Architecture): 스택 기반 VM(JVM, CPython, .NET CLR) vs 레지스터 기반 VM(Dalvik/ART, Lua 5), 바이트코드 포맷, 명령어 디스패치(Dispatch, Switch/Threaded Code).
  • JIT 컴파일 기법 (JIT Compilation): 프로파일 기반 최적화(PGO, Profiling Guided Optimization), Tiered Compilation, 투기적 최적화(Speculative Optimization), 탈최적화(Deoptimization).
  • 인라인 캐시와 핫스팟 (Inline Cache & Hotspot): 모노모픽/폴리모픽 인라인 캐시, 함수 인라이닝, 이스케이프 분석(Escape Analysis).
  • WebAssembly (Wasm): Wasm 바이트코드 포맷, 선형 메모리(Linear Memory), WASI(Wasm System Interface).

Out-of-Scope

  • AOT(Ahead-Of-Time) 컴파일: GraalVM Native Image, Dart AOT → 05-02-02 영역.
  • 가비지 컬렉션 상세: GC 알고리즘 → 05-02-04 Garbage Collection 영역.

Boundaries

  • JIT vs AOT 트레이드오프: JIT는 런타임 프로파일 정보를 활용하여 실제 입력 패턴에 특화된 최적화(투기적 인라이닝, 특정 타입 특화)가 가능하지만 워밍업 시간(Warm-up Time)이 필요합니다. AOT는 워밍업 없이 즉각 최대 성능이지만 런타임 프로파일을 활용할 수 없어 JIT의 정점 성능에 미치지 못하는 경우가 많습니다.

3. Counterexample

  • 투기적 최적화 실패 후 반복 탈최적화 (Deoptimization Storm): JVM이 obj.method()가 항상 타입 A를 갖는다고 추정(Speculation)하여 인라이닝 최적화를 적용한 코드. 갑자기 타입 B의 객체가 obj에 들어오면 추정 실패(Speculation Failure). 해당 메서드 전체가 인터프리터 모드로 탈최적화(Deoptimization)됩니다. 특정 워크로드에서 타입 다양성이 급증하면 탈최적화가 반복되어 성능이 인터프리터 수준으로 하락합니다.
  • 바이트코드 인터프리터의 디스패치 오버헤드 (Interpreter Dispatch Overhead): 스위치 문 기반 바이트코드 인터프리터 switch(opcode) { case LOAD: ... case ADD: ... }. 각 명령어마다 스위치 분기 오버헤드가 발생하고, 분기 예측 실패율이 높아 CPU 파이프라인 정지가 빈번합니다. CPython이 스레드 코드(Threaded Code, goto dispatch_table[opcode]) 디스패치로 교체하면 인터프리터 속도가 20~30% 향상됩니다.

4. Prerequisites

  • JVM 기초 (Basic): 바이트코드, 클래스로더, 힙 메모리 구조 등 JVM 기본 구성. (03. OS & System Mechanics)
  • 컴파일러 최적화 (Recommended): JIT가 적용하는 최적화 패스들은 AOT 최적화와 동일한 기법. (05-02-02 Optimization & Code Generation)

5. Learning Map

Sequence Core Cluster Objective & Description Evidence (BoK)
1 VM Bytecode Architecture JVM 스택 기반 바이트코드와 javap -c로 실제 바이트코드를 읽는 방법, CPython dis 모듈을 익힙니다. P1
2 JIT Tiered Compilation JVM의 인터프리터→C1 JIT→C2 JIT 3단계 티어링과 HotSpot 탐지 카운터의 흐름을 살펴봅니다. P5
3 Speculative Optimization & Deopt 인라인 캐시로 메서드 디스패치를 O(1)로 최적화하고, 추정 실패 시 탈최적화하는 과정을 살펴봅니다. Industry
4 WebAssembly & Future VM Wasm 바이트코드 포맷, 선형 메모리 모델, WASI로 서버사이드 실행하는 VM 방향을 다룹니다. Industry

6. Learning Topics

Basic

Core Topic 01: 플랫폼 독립 실행의 심장, VM 바이트코드 구조 (VM Bytecode Architecture)

  • Why to Learn: "Write Once, Run Anywhere"를 실현하는 JVM의 바이트코드 레이어와, javap -c로 Java 코드가 어떤 바이트코드로 변환되는지를 직접 읽는 역량은 성능 최적화와 JVM 튜닝의 기반입니다.
  • What to Learn:
    • Concepts: 스택 기반 VM(JVM, CPython), 바이트코드 명령어(LOAD_FAST, BINARY_ADD, RETURN_VALUE), 상수 풀(Constant Pool), 메서드 영역(Method Area).
    • Skills: javap -c MyClass.class 바이트코드 분석, python -m dis script.py CPython 바이트코드 분석.
    • Tools: javap -c, python dis 모듈, JVM Bytecode Viewer.
  • How to Learn:
    • 1단계: Python def add(a, b): return a + b. dis.dis(add)LOAD_FAST a; LOAD_FAST b; BINARY_OP +; RETURN_VALUE. 4개의 바이트코드 명령어가 스택 기반으로 a와 b를 스택에 push하고 더한 뒤 결과를 반환하는 과정을 살펴봅니다.
    • 2단계: JVM int add(int a, int b) { return a+b; }. javap -c: iload_1; iload_2; iadd; ireturn. i는 integer 타입 접두사. 자바 타입 시스템이 바이트코드 타입에 직접 반영되는 방식을 살펴봅니다.
  • Implement: 파이썬 import dis; dis.dis(lambda x,y: x*y + 2). 복잡한 람다의 바이트코드 출력. peephole_optimizer 미니 시뮬레이터: LOAD_CONST 2; LOAD_CONST 3; BINARY_OP *LOAD_CONST 6 상수 폴딩으로 3명령 → 1명령 최적화 구현.

Core Topic 02: 따뜻해지는 실행과 JIT의 3단계, Tiered Compilation (JIT Tiered Compilation)

  • Why to Learn: Java 서버를 재시작하면 처음 몇 분간 응답이 느렸다가 점점 빨라지는 "워밍업(Warm-up)" 현상의 원인이 JVM의 계층적 JIT 컴파일(Tiered Compilation)임을 이해하고, JVM 워밍업 시간을 단축하는 실무 기법을 갖추기 위함입니다.
  • What to Learn:
    • Concepts: HotSpot 탐지(호출 카운터 + 루프 백엣지 카운터), 계층 컴파일(Tier 0: 인터프리터, Tier 1/2: C1 빠른 JIT, Tier 3/4: C2 최적화 JIT), OSR(On-Stack Replacement, 실행 중 최적화 전환).
    • Skills: JVM 플래그 -XX:+PrintCompilation으로 JIT 활동 모니터링, -server/-client VM 모드.
  • How to Learn:
    • 1단계: 메서드 hotMethod()가 1만 번 호출되면 HotSpot으로 감지 → C1 JIT 컴파일. 10만 번이면 C2 JIT 컴파일(더 공격적 최적화). 각 단계에서 실행 속도가 단계적으로 향상되는 워밍업 곡선을 살펴봅니다.
    • 2단계: OSR(On-Stack Replacement): 오래 실행되는 루프가 이미 스택에 있는 도중에, 인터프리터 실행 → C2 JIT 최적화 코드로 전환. 루프 실행 중 JIT가 활성화되는 과정을 살펴봅니다.
  • Implement: Java for(long i=0; i<1_000_000_000L; i++) sum+=i; 루프. JVM 워밍업 전(첫 1초) vs 후(10초 이후)의 같은 연산 처리 속도 System.nanoTime() 측정. Python @numba.jit 또는 PyPy로 같은 효과 시뮬레이션.

Practical

Core Topic 03: 추측 최적화와 실패 시 복귀, 인라인 캐시와 탈최적화 (Inline Cache & Deoptimization)

  • Why to Learn: Python의 동적 타입 디스패치(obj.method()가 런타임에야 어떤 클래스인지 결정)를 JIT가 인라인 캐시로 O(1) 모노모픽 디스패치로 최적화하는 V8/JVM의 핵심 기법과, 추정 실패 시 안전하게 복귀하는 탈최적화를 이해하기 위함입니다.
  • What to Learn:
    • Concepts: 인라인 캐시(Inline Cache, IC): 메서드 호출 사이트에 마지막 타입+함수 주소 캐싱, 모노모픽(1 타입, O(1)) vs 폴리모픽(N 타입, O(N)) vs 메가모픽(캐싱 포기) IC.
    • Skills: V8 --print-opt-code로 최적화 코드 확인, JVM -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining.
  • How to Learn:
    • 1단계: 모노모픽 IC: obj.foo() 첫 호출 시 obj가 클래스 A → 캐시에 {A: A::foo} 저장. 다음 호출에서 obj가 A면 캐시 히트 → 직접 점프(O(1)). obj가 B면 캐시 미스 → 폴리모픽 IC 또는 탈최적화가 발생하는 과정을 살펴봅니다.
    • 2단계: 탈최적화(Deopt): JIT가 x는 항상 Int라 추정(Speculate)하여 add(x, y)를 Int 덧셈으로 인라이닝. x가 Float으로 들어오면 Speculation Failure → 해당 메서드의 JIT 코드 파기 → 인터프리터 재실행으로 이어지는 탈최적화 과정을 살펴봅니다.
  • Implement: JavaScript V8 탈최적화 데모. function add(a, b) { return a + b; }. for(let i=0; i<10000; i++) add(i, 1) (Int→JIT). 이후 add("hello", "world") 호출 → V8이 탈최적화. --trace-deopt 플래그로 확인. Python 동적 디스패치 비용: 동일 타입 100만 호출 vs 랜덤 타입 100만 호출 속도 비교.

Advanced

Core Topic 04: 브라우저 바깥의 바이트코드 표준, WebAssembly (WebAssembly & Wasm)

  • Why to Learn: Figma의 UI 렌더링, Google Earth Web, Cloudflare Workers, Docker의 컨테이너 대안으로 떠오르는 WebAssembly가 "JVM처럼 플랫폼 독립적이고, 네이티브 코드처럼 빠른" 범용 VM을 어떻게 실현하는지 이해하기 위해서입니다.
  • What to Learn:
    • Concepts: Wasm 바이트코드 포맷(스택 기반, 타입 안전, 선형 메모리), Wasm 모듈 구조(Type/Function/Import/Export 섹션), WASI(Wasm System Interface, 시스템 콜 추상화), Wasm GC 제안.
    • Skills: emscripten으로 C → Wasm 컴파일, wasmtime CLI로 서버사이드 실행.
    • Tools: emcc, wasmtime, wasm2wat(바이너리 → 텍스트 형식 변환).
  • How to Learn:
    • 1단계: C int add(int a, int b) { return a+b; }emcc -O2 -o add.wasm add.c. wasm2wat add.wasm → 텍스트 형식: (func $add (param i32 i32) (result i32) local.get 0 local.get 1 i32.add). Wasm이 스택 기반 VM의 바이트코드 포맷임을 확인합니다.
    • 2단계: 선형 메모리(Linear Memory): Wasm 모듈은 OS 메모리 주소 공간이 아닌 단일 연속 바이트 배열(Linear Memory)만 접근 가능. 샌드박스 격리가 이 선형 메모리 모델로 보장됩니다. WASI는 이 샌드박스 내에서 파일시스템/네트워크 접근을 선택적으로 허용하는 구조를 살펴봅니다.
  • Implement: Python wasmer 또는 wasmtime 패키지로 Wasm 모듈 실행. 파이썬에서 C로 컴파일된 Wasm 함수 호출: add_module.add(3, 4) == 7 검증. wasmtime CLI로 WASI 파일 접근 권한 제한 데모: wasmtime --dir=./sandbox hello.wasm vs --dir=. 비교.

7. Terminology

Term (EN / ko, abbr) 1문장 정의 단계(기본/권장/실무/심화) 역할/맥락 관련 개념 유사/대비/함께 사용 오해 포인트 Evidence(Primary/Secondary/Industry) Flags(core)
Virtual Machine (VM) 특정 하드웨어(CPU)에 종속되지 않고, 바이트코드를 소프트웨어적으로 읽어 해석(실행)하는 가상의 런타임 컴퓨터(예: JVM, V8)입니다. 기본 플랫폼 독립성 Bytecode / WORA OS VM / Docker OS 레벨의 가상머신(VMware)이 아니라 애플리케이션 레벨의 프로세스 VM임 P1:CS2023 core
Bytecode 인간이 읽는 소스 코드와 CPU가 읽는 기계어의 중간 형태로, 특정 가상 머신(VM) 명령어 집합으로 컴파일된 이진 코드입니다. 권장 플랫폼 비종속 코드 JIT Compilation Machine Code CPU가 직접 이해할 수 없으므로 반드시 VM을 거쳐야 함 P5:SFIA core
Interpreter 바이트코드(또는 소스 코드)를 한 줄씩 읽어서 그때그때 기계어로 변환하며 실행하는 방식(빠른 시작, 느린 실행)입니다. 기본 코드 실행 VM / REPL AOT / JIT Compilation 루프(반복문) 내부를 매번 새로 변환하므로 실행 속도가 매우 느림 Industry core
JIT Compiler (Just-In-Time) 인터프리터의 느린 속도를 극복하기 위해, 프로그램 실행 중에 자주 쓰이는 핫 스팟(Hot Spot) 코드를 기계어로 번역해 캐싱하는 엔진입니다. 심화 런타임 최적화 Hot Spot / AOT Interpreter 프로그램 실행 시간(Run-time) 중에 컴파일 오버헤드가 발생함 Industry core

8. References

Primary

  • [P1] CS2023 - Programming Languages (PL) - Runtime Systems
  • [P5] SFIA - Software Design (SWDN) - Execution Environments

Secondary

  • [Crafting Interpreters] Robert Nystrom - Virtual Machines and Bytecode
  • [The Java Virtual Machine Specification] Tim Lindholm - JVM Architecture

Industry

  • [V8 JavaScript Engine Documentation] - Ignition Interpreter and TurboFan JIT
  • [OpenJDK HotSpot VM] - HotSpot Architecture and Tiered Compilation

9. Final Checklist

Primary

  • AOT(Ahead-Of-Time) 컴파일(C/Rust)과 VM 기반 실행(Java/Python)의 차이를 바이너리 플랫폼 종속성 관점에서 비교할 수 있는가?
  • 인터프리터가 바이트코드를 한 줄씩 해석하는 방식이 왜 루프 문에서 기하급수적인 성능 저하를 일으키는지 설명할 수 있는가?

Secondary

  • JIT(Just-In-Time) 컴파일러가 런타임에 핫 스팟(Hot Spot) 코드를 찾아내어 네이티브 기계어로 캐싱하는 최적화 메커니즘을 증명할 수 있는가?
  • 티어드 컴파일(Tiered Compilation)이 어떻게 빠른 시작(Interpreter)과 높은 런타임 성능(JIT)을 함께 달성하는지 설명할 수 있는가?

Industry

  • V8 엔진이나 JVM에서 JIT 컴파일러가 '타입 추측(Type Speculation)'을 통해 동적 언어의 성능을 높이다가 예측이 틀렸을 때 디옵티마이즈(Deoptimization)하는 과정을 아키텍처 관점으로 묘사할 수 있는가?
  • 런타임 컴파일 오버헤드를 없애기 위해 등장한 GraalVM이나 람다(Lambda) 환경의 콜드 스타트(Cold Start) 문제를 JIT vs AOT 트레이드오프로 저울질할 수 있는가?

Languages Compilers · Runtime Systems & Memory Management

2 / 4