Garbage Collection & Memory Management
Garbage Collection 및 메모리 Management의 정의, 범위, 선행 지식, 학습 주제, 참고 근거를 정리한 CS&E 학습 노드입니다.
Article
M
Me
hyunyoun's Blog
programming-languages-compilersprogramming-languagescompilerscompiler-designimplementationgarbage-collectionmemory-managementlanguages-compilers10 min read
1. Overview
가비지 컬렉션과 메모리 관리(Garbage Collection & Memory Management)는 프로그램이 할당한 메모리를 **더 이상 필요 없게 된 순간 자동으로 회수(GC)**하여, 수동 메모리 관리(C/C++의 malloc/free)의 메모리 누수(Memory Leak)와 댕글링 포인터(Dangling Pointer) 오류 클래스를 제거하는 런타임 시스템 공학입니다.
학습자는 참조 계수(Reference Counting, Python CPython 방식), 마크-스윕(Mark-and-Sweep), 세대별 GC(Generational GC, JVM G1/ZGC), 증분 GC(Incremental GC), **트리컬러 마킹(Tri-color Marking)**의 각 알고리즘의 장단점과 실제 런타임에서의 GC 일시 정지(Stop-the-World Pause) 트레이드오프를 비교합니다. 이어서 Rust의 **소유권 시스템(Ownership + Borrow Checker)**이 GC 없이 컴파일 타임에 메모리 안전성을 보장하는 접근까지 살펴보며 메모리 관리 설계 흐름을 정리합니다.
2. Scope & Boundaries
In-Scope
- 참조 계수 (Reference Counting): Python CPython GC, 순환 참조(Circular Reference) 문제,
gc.collect(). - 마크-스윕 (Mark-and-Sweep): 루트 집합(Root Set), DFS 마킹, 스윕 단계, Stop-the-World.
- 세대별 GC (Generational GC): 약한 세대 가설(Weak Generational Hypothesis), Young/Old Generation, Minor/Major GC, JVM G1 GC.
- 동시 GC (Concurrent GC): Tri-color Marking(흰색/회색/검정색), ZGC/Shenandoah, Write Barrier.
- Rust 소유권 (Rust Ownership): 소유권 규칙, 빌림(Borrow), 수명(Lifetime), GC 없는 메모리 안전성.
Out-of-Scope
- 메모리 할당기 구현 (Allocator):
malloc구현, Buddy System, Slab Allocator → 03-01 Hardware-OS Interface 영역. - JVM GC 튜닝 명령어:
-Xms, -Xmx, -XX:G1HeapRegionSize실무 튜닝 → 05-03-03 JVM Core 영역.
Boundaries
- GC vs 수동 관리 vs Rust 소유권: GC는 개발 생산성이 높고 메모리 오류가 없지만 GC 일시 정지(STW)로 지연 시간(Latency) 예측이 어렵습니다. C/C++ 수동 관리는 지연 없이 결정론적 성능이지만 개발자가 모든 해제를 책임져야 합니다. Rust 소유권은 GC 없이 컴파일 타임 메모리 안전성을 보장하여 두 장점을 결합하지만 학습 곡선이 높습니다.
3. Counterexample
- 참조 계수의 순환 참조 메모리 누수 (Circular Reference Leak in RefCounting): Python에서
a = Node(); b = Node(); a.next = b; b.prev = a; del a; del b. a와 b의 외부 참조를 제거했지만, a.next=b, b.prev=a로 서로를 가리키는 순환 참조가 남아 참조 계수가 0이 되지 않습니다. CPython의 단순 참조 계수만으로는 이 순환이 회수되지 않아 메모리 누수가 발생합니다. CPython이 별도의 사이클 탐지 GC(gc.collect())를 추가로 실행하는 이유입니다. - Stop-the-World GC 일시 정지의 P99 지연 급등 (GC Pause Latency Spike): Java 배치 처리 서버에서 JVM Young Generation이 꽉 차서 Minor GC 발생. Stop-the-World로 모든 스레드가 수백 ms 동안 멈춥니다. 초당 1000 요청을 처리하는 서버에서 500ms GC 일시 정지는 500개 요청의 응답 시간이 P99에서 500ms 급등하는 지연 요인입니다. 저지연 GC(ZGC, Shenandoah)로 STW 시간을 <10ms로 제한해야 합니다.
4. Prerequisites
- 포인터와 힙 메모리 (Basic): 동적 할당(
malloc)과 힙 메모리의 수명(Lifetime) 개념. (03-01 Memory & CPU Architecture) - 그래프 탐색(DFS) (Basic): 마크-스윕에서 루트 집합에서 DFS로 도달 가능 객체를 마킹합니다. (04-04-01 Graph Foundations)
5. Learning Map
6. Learning Topics
Basic
Core Topic 01: 참조 횟수가 0이 되면 해제, 참조 계수 GC (Reference Counting)
- Why to Learn: CPython(파이썬 기본 구현), PHP, Swift의 ARC(Automatic Reference Counting)가 채택한 참조 계수의 즉각 해제 장점과, 왜 Python이 별도의 사이클 GC(
gc모듈)를 추가로 실행해야 하는지를 이해하기 위함입니다. - What to Learn:
- Concepts: 참조 계수(Reference Count, RC), 즉각 해제(Immediate Deallocation), 순환 참조(Cycle),
sys.getrefcount(). - Skills: 파이썬
gc.collect()수동 사이클 회수,weakref로 순환 참조 방지. - Tools: Python
sys.getrefcount(),gc모듈.
- Concepts: 참조 계수(Reference Count, RC), 즉각 해제(Immediate Deallocation), 순환 참조(Cycle),
- How to Learn:
- 1단계:
x = [1, 2, 3]; sys.getrefcount(x)→ 2 (x 참조 + getrefcount 인자).y = x; sys.getrefcount(x)→ 3.del y; sys.getrefcount(x)→ 2.del x→ RC=0, 즉시 해제되는 과정을 살펴봅니다. - 2단계: 순환 참조:
a={}; b={}; a['other']=b; b['other']=a; del a; del b.gc.get_objects()로 미회수 딕셔너리 2개 확인.gc.collect()호출 후 회수 완료 과정을 살펴봅니다.
- 1단계:
- Implement: 파이썬
track_allocations()컨텍스트 매니저.__enter__에서 GC 객체 수 기록,__exit__에서 차이 출력. 순환 참조 생성 전후 GC 객체 수 변화 +gc.collect()효과 측정.weakref.ref(a)로 weak 참조 후 순환 참조 없이 즉시 해제됨 증명.
Recommended
Core Topic 02: 도달 불가능한 쓰레기의 청소, 마크-스윕과 세대별 GC (Mark-and-Sweep & Generational GC)
- Why to Learn: JVM의 Minor/Major GC가 왜 주기적으로 발생하고, 세대를 분리하여 GC 빈도와 비용을 줄이는 세대별 GC의 핵심 가설(약한 세대 가설)이 현실에서 왜 잘 작동하는지를 이해하기 위함입니다.
- What to Learn:
- Concepts: 루트 집합(Root Set: 전역 변수, 스택 변수, 레지스터), 마크 단계(DFS 도달 가능 객체 표시), 스윕 단계(미표시 객체 회수), STW(Stop-the-World), 약한 세대 가설(대부분의 객체는 일찍 죽는다).
- Skills: JVM Young Generation(Eden + S0/S1 Survivor) → Old Generation 승진(Promotion) 흐름.
- How to Learn:
- 1단계: 마크-스윕: 힙에 A, B, C, D 객체. 루트 → A → B. C, D는 루트 미도달. 마킹: A, B 표시. 스윕: C, D 회수. STW 동안 어플리케이션 스레드 전체가 중단되는 이유를 살펴봅니다.
- 2단계: 세대별 GC: 새 객체 → Eden. Minor GC: Eden + Survivor 마크-스윕. 살아남은 객체 → Survivor. 여러 번(기본 15회) 살아남은 객체 → Old Generation. Old가 꽉 차면 Major GC(전체 힙) 발생. 대부분 객체가 Eden에서 죽어 Minor GC 비용이 작아지는 세대 가설 효과를 살펴봅니다.
- Implement: 파이썬 마크-스윕 시뮬레이터. 객체 그래프를 인접 리스트로 표현.
mark(roots, graph)DFS 마킹.sweep(all_objects, marked)미마킹 객체 회수.[A→B, C→D, E(root)→A]에서roots=[E]로C, D회수 시뮬레이션.
Practical
Core Topic 03: GC와 앱이 동시에, 삼색 마킹과 동시 GC (Concurrent GC & Tri-color Marking)
- Why to Learn: JVM G1/ZGC, Go GC, Ruby 최신 GC가 채택한 동시 GC(Concurrent GC)가 STW 일시 정지를 밀리초 수준으로 단축하는 삼색 마킹의 정확성 보장(Invariant)과 쓰기 장벽(Write Barrier)의 역할을 이해하기 위함입니다.
- What to Learn:
- Concepts: 삼색 불변식(Tri-color Invariant): 흰색(미방문, 수집 대상), 회색(방문 중, 큐에 대기), 검정색(방문 완료, 안전). 쓰기 장벽(Write Barrier, Dijkstra/SATB Barrier), Incremental Update vs SATB.
- Skills: Go
GOGC환경변수로 GC 트리거 임계값 조정, JVM-XX:+UseZGC설정. - Tools: JVM GC 로그
-Xlog:gc:file=gc.log, GoGODEBUG=gctrace=1.
- How to Learn:
- 1단계: 삼색 GC: 흰색 객체 전체로 시작. GC 스레드가 루트에서 DFS. 방문 큐에 추가 시 회색. 자식 모두 처리 후 검정색. GC 완료 시 흰색만 회수. 앱 스레드와 GC 스레드가 동시에 실행되는 동시 마킹 흐름을 살펴봅니다.
- 2단계: 쓰기 장벽: 앱 스레드가
검정객체.ref = 흰색객체포인터를 쓸 때, 쓰기 장벽이 해당 흰색 객체를 회색으로 다시 색칠하여 GC가 수집하지 않도록 보호합니다. 이 장벽 없이 동시 GC를 실행하면 살아있는 객체를 수집하는 GC 버그가 발생하는 이유를 살펴봅니다.
- Implement: 파이썬 삼색 마킹 시뮬레이터.
WHITE, GRAY, BLACK상태 집합.mark_phase(graph, roots): BFS로 루트에서 회색 → 검정색 순차 처리.sweep_phase(): WHITE 회수. 앱 스레드 포인터 변경 시 쓰기 장벽으로 WHITE → GRAY 재색칠 시뮬레이션.
Advanced
Core Topic 04: 컴파일러가 메모리를 결정한다, Rust 소유권과 수명 (Rust Ownership & Lifetimes)
- Why to Learn: GC 일시 정지(STW) 없이, 수동 관리의 Dangling Pointer 위험 없이, 컴파일 타임에 100% 메모리 안전성을 보장하는 Rust의 소유권 + 빌림 검사기가 시스템 프로그래밍에서 중요한 이유를 이해하기 위해서입니다.
- What to Learn:
- Concepts: 소유권(Ownership: 한 값에 한 소유자), 이동(Move, 소유권 전이), 불변 빌림(
&T), 가변 빌림(&mut T, 동시에 단 1개), 수명(Lifetime,'a), 수명 생략 규칙(Elision). - Skills:
&,&mut,Box<T>,Rc<T>,Arc<T>선택 기준, 수명 어노테이션. - Tools: Rust
cargo,rustc --edition=2021.
- Concepts: 소유권(Ownership: 한 값에 한 소유자), 이동(Move, 소유권 전이), 불변 빌림(
- How to Learn:
- 1단계: 소유권 이동:
let s1 = String::from("hello"); let s2 = s1;. s1의 소유권이 s2로 이동(Move). 이후println!("{}", s1)→ 컴파일 오류 "borrow of moved value". s1은 더 이상 유효하지 않아 이중 해제(Double Free) 버그가 컴파일 타임에 차단되는 과정을 살펴봅니다. - 2단계: 빌림 규칙:
fn print_str(s: &str)→ 불변 빌림, 함수 종료 후 소유권 반환.fn modify(s: &mut String)→ 가변 빌림. 가변 빌림이 활성화된 동안 다른 어떤 빌림도 불가. 데이터 경쟁(Data Race)을 컴파일 타임에 차단하는 빌림 검사기(Borrow Checker)를 살펴봅니다.
- 1단계: 소유권 이동:
- Implement: Rust(또는 파이썬 소유권 시뮬레이션). 파이썬
Owner클래스:move_to(other)후 기존 객체 접근 시UseAfterMove예외.BorrowChecker:immutable_borrow()여러 번 동시 허용,mutable_borrow()단 1개 제한 + 이미 불변 빌림 있으면 가변 불가 로직 구현. 소유권/빌림 위반 케이스 자동 탐지 증명.
7. Terminology
8. References
Primary
- [P1] CS2023 - Programming Languages (PL) - Memory Management
- [P5] SFIA - Software Performance Engineering - Resource Management
Secondary
- [The Garbage Collection Handbook] Richard Jones - Mark-Sweep, Generational, and Concurrent GC
- [Operating Systems: Three Easy Pieces] Remzi Arpaci-Dusseau - Heap Memory Management
Industry
- [Oracle Java Documentation] - Garbage Collection Tuning (G1GC, ZGC)
- [Python Developer's Guide] - CPython Memory Management and GIL
9. Final Checklist
Primary
- 정적 할당(Stack)과 동적 할당(Heap)의 차이를 설명하고, 왜 Heap 영역에 수동 또는 자동 메모리 해제가 필요한지 논증할 수 있는가?
- 참조 카운팅(Reference Counting) 기법이 순환 참조(Cyclic Reference) 상황에서 어떻게 메모리 누수(Memory Leak)를 일으키는지 증명할 수 있는가?
Secondary
- Mark-and-Sweep 알고리즘에서 GC 루트(Roots)의 개념을 설명하고, 도달 불가능(Unreachable) 상태 판별 기준을 설명할 수 있는가?
- 약한 세대 가설(Weak Generational Hypothesis)을 기반으로 Young Generation과 Old Generation이 물리적으로 어떻게 나뉘어 동작하는지 묘사할 수 있는가?
Industry
- GC 실행 중 발생하는 Stop-The-World (STW) 현상이 대규모 백엔드 트래픽(TPS) 처리 지연에 미치는 물리적 영향을 아키텍처 관점으로 설계할 수 있는가?
- G1GC나 ZGC 같은 최신 가비지 컬렉터가 수십 GB의 힙(Heap)에서도 밀리초 단위의 짧은 정지 시간(Pause Time)을 달성하는 동시성(Concurrent) 원리를 저울질할 수 있는가?