콘텐츠로 바로가기

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

Sequence Core Cluster Objective & Description Evidence (BoK)
1 Reference Counting 참조 계수의 즉각 회수 장점과 순환 참조 메모리 누수, 파이썬 GC 사이클 탐지를 익힙니다. P1
2 Mark-and-Sweep & Generational GC 도달 불가능 객체를 DFS로 마킹 후 스윕하는 흐름과, Young/Old 세대 분리로 효율을 높이는 세대별 GC를 살펴봅니다. P5
3 Concurrent GC & Tri-color Marking STW 없이 GC와 어플리케이션이 동시 실행하는 삼색 마킹과 쓰기 장벽의 동작을 살펴봅니다. Industry
4 Rust Ownership & Lifetimes 소유권 규칙(한 값에 한 소유자), 빌림 검사기(Borrow Checker), 수명(Lifetime)으로 GC 없이 메모리 안전성을 확보하는 방식을 다룹니다. Industry

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 모듈.
  • 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() 호출 후 회수 완료 과정을 살펴봅니다.
  • Implement: 파이썬 track_allocations() 컨텍스트 매니저. __enter__에서 GC 객체 수 기록, __exit__에서 차이 출력. 순환 참조 생성 전후 GC 객체 수 변화 + gc.collect() 효과 측정. weakref.ref(a) 로 weak 참조 후 순환 참조 없이 즉시 해제됨 증명.

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, Go GODEBUG=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.
  • 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)를 살펴봅니다.
  • Implement: Rust(또는 파이썬 소유권 시뮬레이션). 파이썬 Owner 클래스: move_to(other) 후 기존 객체 접근 시 UseAfterMove 예외. BorrowChecker: immutable_borrow() 여러 번 동시 허용, mutable_borrow() 단 1개 제한 + 이미 불변 빌림 있으면 가변 불가 로직 구현. 소유권/빌림 위반 케이스 자동 탐지 증명.

7. Terminology

Term (EN / ko, abbr) 1문장 정의 단계(기본/권장/실무/심화) 역할/맥락 관련 개념 유사/대비/함께 사용 오해 포인트 Evidence(Primary/Secondary/Industry) Flags(core)
Garbage Collection (GC) 프로그램이 런타임에 동적으로 할당한 메모리(Heap) 중 더 이상 사용되지 않는 영역을 탐지하고 자동으로 회수하는 메모리 관리 엔진입니다. 기본 메모리 자동화 Heap Memory Manual Memory Mgmt (C/C++) 모든 메모리 누수(Memory Leak)를 막지는 못하며, 누수가 여전히 발생할 수 있음 P1:CS2023 core
Reference Counting 객체가 참조될 때마다 카운트를 올리고, 참조가 끊기면 0이 되어 즉시 메모리를 해제하는 가장 단순한 형태의 메모리 관리 기법(Python, Swift 적용)입니다. 권장 메모리 추적 Cyclic Reference Tracing GC (Mark-and-Sweep) A가 B를 참조하고 B가 A를 참조하는 '순환 참조'에서는 카운트가 0이 되지 않아 누수 발생 P5:SFIA core
Mark-and-Sweep 루트(스택/전역변수)부터 시작해 도달 가능한 모든 객체를 마킹(Mark)한 뒤, 마킹되지 않은 객체를 회수하는(Sweep) 추적 기반 GC 알고리즘입니다. 실무 동적 메모리 회수 Stop-The-World (STW) Reference Counting 회수 후 메모리 조각화(Fragmentation)가 발생하므로 Compaction이 필요함 Industry core
Generational GC "대부분의 객체는 금방 죽는다"는 가설을 바탕으로 메모리를 Young과 Old 세대로 나누어, Young 영역만 자주 청소해 GC 오버헤드를 낮춘 알고리즘입니다. 심화 GC 성능 최적화 Minor/Major GC ZGC / G1GC Old 영역을 청소하는 Major GC(Full GC) 발생 시 긴 정지 시간(STW)이 발생함 Industry core

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) 원리를 저울질할 수 있는가?

Languages Compilers · Compiler Design & Implementation

4 / 4