Runtime Systems & Memory Management
프로그램 실행을 관리하는 가상 머신, 인터프리터 루프, 그리고 자동 메모리 관리(GC) 알고리즘의 물리적 거동을 다루는 학습 노드입니다.
Article
M
Me
hyunyoun's Blog
programming-languages-compilersprogramming-languagescompilersruntime-systemsmemory-managementlanguages-compilerslearningallocation-strategies9 min read
1. Overview
런타임 시스템 및 메모리 관리 최적화(Runtime Systems & Memory Management Optimization, RMO)는 프로그램이 디스크의 정적인 바이너리에서 메모리에 적재된 프로세스로 실행된 뒤 일어나는 동적 동작과 런타임 환경(Runtime Environment)의 내부 메커니즘을 다룹니다.
C/C++처럼 컴파일러가 대부분의 실행 준비를 미리 끝내는 언어와 달리, Java, Python, JavaScript 같은 현대 언어들은 가상 머신(VM)과 런타임 엔진이 실행 과정에 계속 관여합니다. 학습자는 바이트코드(Bytecode)를 한 줄씩 읽어 들이는 인터프리터(Interpreter)의 루프를 이해하고, 자주 쓰이는 코드를 런타임에 기계어로 번역하는 JIT(Just-In-Time) 컴파일러의 동작을 배웁니다. 또한 수백만 개의 객체가 생성되고 버려지는 힙(Heap) 메모리 공간에서, 더 이상 쓰이지 않는 객체(Garbage)를 찾아 회수하는 GC(Garbage Collection)의 세대별(Generational) 수거 방식과 Stop-the-World 지연(Latency) 통제 기술을 살펴봅니다.
2. Scope & Boundaries
In-Scope
- 가상 머신과 실행 엔진 (Virtual Execution): 스택 기반 VM(JVM) vs 레지스터 기반 VM(Dalvik, V8), 바이트코드 포맷, 인터프리터 루프(Dispatch Loop) 오버헤드.
- 동적 컴파일링 (Dynamic Compilation): JIT 컴파일러의 프로파일링(Profiling) 기반 핫스팟(Hot Spot) 최적화, 온스택 리플레이스먼트(OSR, On-Stack Replacement), 인라인 캐시(Inline Cache).
- 자동 메모리 수거 기초 (GC Foundations): 도달 가능성(Reachability) 분석, 루트 셋(Root Set), 참조 카운팅(Reference Counting), 순환 참조(Circular Reference) 파괴.
- 고급 가비지 컬렉션 물리 (Advanced GC Mechanics): Mark-and-Sweep, Mark-and-Compact, 세대별 GC(Young/Old Generation, Eden/Survivor), 무정지(Concurrent) GC의 쓰기 장벽(Write Barrier).
Out-of-Scope
- 운영체제 레벨의 페이지 교체 알고리즘: 페이징(Paging), 세그먼테이션(Segmentation), TLB(Translation Lookaside Buffer) 미스 처리 → 03. Operating Systems 영역으로 위임.
- 정적 컴파일 파이프라인: 구문 분석(Parsing)부터 LLVM IR을 통한 사전(AOT) 최적화 단계 → 05-02. Compiler Design 영역으로 위임.
Boundaries
- RMO vs. LPNS (05-04): LPNS가 C/Rust처럼 '개발자가 직접' 힙 메모리의 수명을 통제하고 하드웨어에 밀착하는 기술이라면, RMO는 '시스템(VM/GC)이 대신' 메모리 수명을 추적하고 코드를 런타임에 재단해 주는 자동화된 환경의 물리적 오버헤드와 튜닝 포인트를 다룹니다.
3. Counterexample
- GC를 과신한 메모리 누수 간과: "Java나 Python은 가비지 컬렉터가 있으니 메모리 누수(Memory Leak)가 없다"고 단정하는 사례. 더 이상 사용하지 않는 거대한 객체 리스트를 전역 변수(Static)나 캐시용 맵(Map)에 계속 담아두면, GC는 이를 '여전히 사용 중인 도달 가능한(Reachable) 객체'로 판정하여 회수하지 않고 결국 OutOfMemoryError(OOM)를 일으키는 논리적 누수 현상을 파악해야 합니다.
- 단순 벤치마크 루프를 통한 JIT 오판: "for문을 10번 돌려봤더니 Java가 C보다 훨씬 느리다"고 단정하는 것. JIT 컴파일러는 초반 실행 시 인터프리터로 코드를 분석(Warm-up)하며 프로파일링 데이터를 쌓다가, 특정 횟수 이상(예: 1만 번) 루프가 반복되면 C언어에 가까운 기계어를 생성하는 지연 최적화(Lazy Optimization) 특성을 가지므로, 런타임 특성을 무시한 정적 벤치마크는 무의미합니다.
4. Prerequisites
- 컴파일러 설계 (Basic): JIT 엔진을 이해하려면 런타임에 수행되는 코드 제너레이션과 레지스터 할당 원리를 알아야 합니다. (05-02. CDI)
- 메모리 계층 구조 (Recommended): GC가 객체를 압축(Compaction)할 때 CPU 캐시 지역성(Cache Locality)이 회복되는 원리를 이해하려면 컴퓨터 아키텍처 기초가 필요합니다. (02. Computer Architecture)
5. Learning Map
6. Learning Topics
Basic
Core Topic 01: 실행 엔진과 가상 머신 아키텍처 (VM Execution Models)
- Why to Learn: 하드웨어 CPU마다 컴파일을 따로 하지 않고 'Write Once, Run Anywhere'를 달성하는 이면에 존재하는 소프트웨어 번역 루프의 오버헤드를 체감하기 위함입니다.
- What to Learn:
- Concepts: 가상 머신(Virtual Machine), 바이트코드(Bytecode), 명령어 디스패치(Instruction Dispatch).
- Skills: 스택 기반 VM(Stack-based, 피연산자를 스택에 푸시/팝) vs 레지스터 기반 VM(Register-based, 가상 레지스터 배열 직접 접근) 물리 동작 비교.
- Tools: 바이트코드 디스어셈블러(Java
javap, Pythondis). - Trade-offs: 스택 머신의 컴파일러 제작 용이성과 컴팩트한 바이트코드 크기 vs 레지스터 머신의 적은 명령어 개수와 빠른 디스패치 루프 속도 간의 타협.
- How to Learn:
- 1단계: 라는 연산을 스택 머신용 명령어(
ILOAD A,ILOAD B,IADD,ISTORE C)와 레지스터 머신용 명령어(ADD R3, R1, R2)로 변환해 메모리 접근 횟수를 비교합니다. - 2단계: 거대한
switch-case문으로 바이트코드를 읽어 들이는 인터프리터 코드를 작성해 보고, 매번 분기문에서 발생하는 CPU 파이프라인 플러시(Flush) 패널티를 인식합니다.
- 1단계: 라는 연산을 스택 머신용 명령어(
- Implement: 10여 개의 커스텀 바이트코드(PUSH, POP, ADD, JMP)를 받아 스택 메모리를 갱신하며 결과값을 출력하는 초소형 가상 머신(VM) 엔진 C 코드 구현.
Recommended
Core Topic 02: JIT 컴파일러와 동적 최적화 (Dynamic JIT & Profiling)
- Why to Learn: 처음에는 느리게 실행되던 코드가 몇 초 뒤 빠르게 가속되는 현대 브라우저(V8)나 서버(JVM)의 동적 기계어 생성 방식을 이해하기 위해서입니다.
- What to Learn:
- Concepts: JIT(Just-In-Time) 컴파일, AOT(Ahead-Of-Time), 웜업(Warm-up), 핫스팟(Hotspot) 프로파일링.
- Skills: 온스택 리플레이스먼트(On-Stack Replacement, OSR: 실행 중인 루프 중간에 기계어 실행으로 전환), 다형성 인라인 캐시(Polymorphic Inline Cache, PIC)를 통한 동적 타입 탐색 오버헤드 감소.
- Tools: JVM 티어드 컴파일레이션(Tiered Compilation) 로그 분석 도구.
- Trade-offs: 즉시 시작 가능하나 실행이 느린 인터프리터 vs 실행은 빠르지만 컴파일 비용 때문에 시작이 느린 JIT 컴파일러의 단점을 상호 보완하는 다단계(Tiered) 실행 체제.
- How to Learn:
- 1단계: 동적 타입 언어에서
obj.method()를 호출할 때마다 프로토타입 체인을 탐색하는 비용을, JIT가 "이전에 호출된 객체 타입이 A였으니 이번에도 A일 것이다"라고 추측해 조건문 하나(Inline Cache)로 캐싱하는 과정을 도식화합니다. - 2단계: 최적화가 잘못되었다는 것(Deoptimization)이 판명 났을 때 기계어 코드 실행을 즉시 멈추고 안전하게 인터프리터로 롤백하는 가드(Guard) 조건을 연구합니다.
- 1단계: 동적 타입 언어에서
- Implement: 특정 함수가 10회 이상 호출될 때마다 호출 카운트를 올리고, 임계치 도달 시 함수 포인터를 '빠른 모의 함수'로 교체하는 단순 핫스팟 JIT 시뮬레이터.
Practical
Core Topic 03: 가비지 컬렉션의 추적과 회수 물리 (GC Foundations)
- Why to Learn:
malloc과free를 직접 관리하는 부담을 줄여 주는 자동 수거기가 어떤 알고리즘으로 살아있는 객체와 회수 대상 객체를 구분하는지 알아야 힙(Heap) 튜닝이 가능해지기 때문입니다. - What to Learn:
- Concepts: 루트 셋(Root Set: 전역 변수, 스택 지역 변수), 도달 가능성(Reachability), 파편화(Fragmentation).
- Skills: 참조 카운팅(Reference Counting)과 순환 참조(A B A)의 한계 증명, Tracing GC(Mark-and-Sweep)의 재귀적 마킹 흐름, 복사(Copying) GC의 From/To-Space 분할 압축 기법.
- Tools: 힙 덤프(Heap Dump) 분석기(MAT).
- Trade-offs: 객체가 소멸 즉시 메모리를 회수하는 참조 카운팅의 낮은 최대 지연 시간 vs 참조 카운터 증감 시 발생하는 캐시 라인(Cache Line) 무효화 및 순환 참조 누수라는 한계.
- How to Learn:
- 1단계: 10개의 객체가 서로 얽힌 객체 그래프를 그리고, 스택에 연결된 루트 셋에서 출발해 도달 불가능한 '섬(Island of Garbage)' 객체들을 Mark-and-Sweep으로 찾아내어 비트맵을 0으로 지우는 훈련을 합니다.
- 2단계: 메모리 파편화로 인해 10MB 여유 공간이 있음에도 2MB짜리 배열을 할당하지 못하는 상황을 막기 위해, 살아남은 객체들을 메모리 한쪽으로 촘촘하게 복사해 넣는(Compaction) 비용 공식을 세웁니다.
- Implement:
struct Object { int mark; ... }기반의 가상 힙 리스트를 순회하며 루트 노드부터 재귀적으로mark=1을 찍은 뒤,mark=0인 객체들의 메모리를 전부 해제하는 기초 마크-앤-스위프(Mark & Sweep) 엔진.
Advanced
Core Topic 04: 세대별 힙과 저지연 무정지 GC (Generational & Low-Latency GC)
- Why to Learn: 수백 GB의 대용량 메모리를 쓰는 데이터베이스나 초저지연 거래 시스템에서, 프로그램이 몇 초씩 멈추는 Stop-The-World(STW) 현상을 수 밀리초 단위로 억제하기 위함입니다.
- What to Learn:
- Concepts: 약한 세대 가설(Weak Generational Hypothesis), 에덴(Eden)과 서바이버(Survivor), 테뉴어드(Tenured/Old) 영역, 쓰기 장벽(Write Barrier).
- Skills: Minor GC(Young 영역 중심 수집) vs Major GC(전체 힙 수집)의 발동 조건 제어, 동시성 마킹(Concurrent Marking) 시 애플리케이션 스레드(Mutator)가 포인터를 바꿀 때 발생하는 마킹 누락 문제(Lost Object) 해결 방식.
- Tools: GC 로그 뷰어(GCEasy), JVM 플래그 튜닝(
-XX:+UseG1GC,-Xmx). - Trade-offs: STW를 없애기 위해 애플리케이션 실행과 동시에 GC를 수행할 때 들어가는 추가 CPU 연산(읽기/쓰기 배리어) 오버헤드(Throughput 감소) vs STW를 1ms 이하로 줄이는 지연 시간(Latency) 통제 능력.
- How to Learn:
- 1단계: "새로 생성된 객체는 금방 쓰레기가 되고, 오래 살아남은 객체는 계속 살아남는다"는 세대 가설을 바탕으로, 전체 힙을 뒤지지 않고 Young 영역을 중심으로 수집해 GC 시간을 90% 단축하는 세대 분리 파티셔닝 구조를 설계합니다.
- 2단계: GC가 객체 A를 검사 완료했는데, 동시에 실행 중인 앱 스레드가 검사 안 한 B를 A 밑으로 옮겨 B가 잘못 회수될 수 있는 상황(삼색 마킹 트리 위반)을 쓰기 장벽(Write Barrier) 코드가 어떻게 차단하는지 논리 흐름을 추적합니다.
- Implement: 메모리 할당 속도와 객체 소멸 확률 파라미터를 입력받아, 세대별 GC 환경에서 Young 영역이 가득 찼을 때 객체들이 Survivor 공간을 오가며 Age가 증가하고 Old 영역으로 승급(Promotion)하는 과정을 모사한 시뮬레이터.
7. Terminology
8. References
Primary References
- [P1] CS2023 - PL/Compilers & Runtimes — GC and runtime structures.
- [P2] SWEBOK - Software Construction — Memory management techniques.
Secondary References
- [The Garbage Collection Handbook] Richard Jones et al. — The definitive GC authority.
- [Virtual Machines] James Smith & Ravi Nair — Hardware/Software runtime architecture.
Industry References
- [V8 JavaScript Engine Architecture] — Real-world high-performance runtime.
- [HotSpot JVM Internal Wiki] — Deep dive into production JIT and GC.
9. Final Checklist
Primary Checklist
- 참조 횟수 계산(Reference Counting) 방식이 순환 참조 상황에서 메모리 누수를 일으키는 물리적 이유를 설명 가능한가? (P1)
- 세대별 GC에서 'Survivor' 공간이 두 개(S0, S1) 필요한 이유를 메모리 복사 관점에서 기술할 수 있는가? (P1)
Secondary Checklist
- JIT 컴파일러가 인터프리터보다 속도는 빠르지만 초기 구동 시간(Warm-up)과 메모리 점유 면에서 불리한 이유를 인지하는가?
- Stop-the-World 시간이 시스템의 응답 속도(Latency)와 사용자 경험에 미치는 물리적 영향력을 평가하고 있는가?
Industry Checklist
- 서비스 환경의 힙 메모리 사용량 로그를 분석하여 Full GC 발생 빈도를 조절하기 위한 JVM 튜닝 제안이 가능한가? (SFIA)
- 애플리케이션의 객체 할당 속도(Allocation Rate)가 GC 스레드 처리 속도를 넘어서는 물리적 위기 상황을 감지할 수 있는가?