[Java] 가비지 컬렉터(Garbage Collection)
📌 들어가며
C/C++에서는 개발자가 메모리 할당과 해제를 일일이 직접 관리해야 한다. 실수하면 메모리 누수나 크래시로 이어진다. 자바는 이 부담을 가비지 컬렉터(GC)에게 맡긴다.
GC(Garbage Collection)란? JVM의 Heap 영역에서 동적으로 할당했던 메모리 중 더 이상 필요 없는 영역을 주기적으로 삭제하는 프로세스. 덕분에 개발자가 수동으로 메모리를 해제할 필요가 없다.
1
2
// System.gc() — GC 실행을 '제안'한다 (강제는 아님)
// Runtime.getRuntime().gc() 와 사실상 동일
⚠️ GC의 단점
- 메모리가 언제 해제되는지 개발자가 정확히 알 수 없다.
- GC가 동작하는 동안 다른 작업이 멈춰 오버헤드가 발생한다.
1. Reachable vs Unreachable
객체는 실제로 Heap 영역에 생성되고, Root Area(스택 등)는 그 객체의 주소만 참조한다. 참조 여부에 따라 상태가 갈린다.
| 상태 | 의미 | GC 대상 |
|---|---|---|
| Reachable | 어딘가에서 참조되고 있음 | ❌ |
| Unreachable | 아무 데서도 참조되지 않음 | ✅ (제거 대상) |
GC는 Unreachable 상태의 객체를 주기적으로 제거한다.
2. Mark And Sweep 알고리즘
GC의 동작 원리로, Root에서 객체에 접근 가능한지를 메모리 해제의 기준으로 삼는다. 3단계로 진행된다.
| 단계 | 동작 |
|---|---|
| ① Mark | Root부터 그래프를 순회하며 참조되는(연결된) 객체를 표시 |
| ② Sweep | 표시되지 않은(Unreachable) 객체를 Heap에서 제거 |
| ③ Compact | Sweep 후 흩어진 객체를 Heap 시작 주소로 모아 압축 |
3. GC 대상 Heap 영역 — 세대(Generation)
효율적인 GC를 위해 Heap은 세대별로 나뉜다.
1
2
3
4
5
┌──────────── Young Generation ────────────┐ ┌─ Old ─┐ ┌ Metaspace ┐
│ Eden Survivor0 Survivor1 │ │ │ │ (meta 정보)│
└───────────────────────────────────────────┘ └───────┘ └───────────┘
최초 생성 살아남으면 이동 오래 살아남으면
promotion
| 영역 | 설명 | GC 종류 |
|---|---|---|
| Eden | 객체가 최초로 생성되는 곳 | Minor GC |
| Survivor 0/1 | Eden에서 살아남은 객체가 이동 | Minor GC |
| Old Generation | 오래 살아남은 객체가 이동(promotion) | Major GC |
| Metaspace (JDK 1.8+) | 클래스 meta 정보, intern 문자열 등 | — |
| GC 종류 | 대상 | 특징 |
|---|---|---|
| Minor GC | Young Generation | 빠름, 빈번 |
| Major GC | Old Generation | 느림, Stop-the-world 발생 |
⚠️ Stop-the-world: Major GC가 실행되면 GC를 제외한 모든 스레드가 작업을 멈춘다. 이 작업이 잦으면 성능 문제가 된다.
4. 동작 과정 (age-bit)
객체는 Eden에서 태어나 Minor GC를 살아남을 때마다 age-bit가 1씩 증가하고, 임계값에 도달하면 Old로 승격된다.
1
2
3
4
5
① Eden 생성 (age 0)
② Eden 꽉 참 → Minor GC → 살아남으면 Survivor로 이동 (age++)
③ 반복하며 Survivor0 ↔ Survivor1 사이 이동, age 계속 증가
④ age-bit가 임계값 도달 → Old로 이동 (promotion)
⑤ Old도 꽉 참 → Major GC (Stop-the-world)
| 과정 | 이미지 |
|---|---|
| Eden에 age 0으로 할당 | ![]() |
| Eden 꽉 참 → Minor GC | ![]() |
| Survivor로 이동, age++ | ![]() |
| Survivor 교차 이동 | ![]() |
| 임계값 도달 → promotion | ![]() |
| Old 꽉 참 → Major GC | ![]() |
5. GC Policy — 어떤 방식으로 수거할까
Old 영역이 가득 차 Major GC가 동작할 때, 어떤 방식으로 수행하느냐가 성능에 큰 영향을 준다.
| 정책 | 옵션 | 특징 |
|---|---|---|
| Serial GC | -XX:+UseSerialGC | Mark&Sweep, 단일 스레드. 메모리·코어 적을 때 |
| Parallel GC | -XX:+UseParallelGC | Serial과 같은 알고리즘을 여러 스레드로. Throughput GC |
| Parallel Old GC | -XX:+UseParallelOldGC | Old 영역에 Mark&Summary&Compaction 적용 |
| CMS GC | -XX:+UseConcMarkSweepGC | STW 최소화(동시 수행), 단 CPU·메모리 더 쓰고 compaction 미제공 |
| G1GC | — | Heap을 바둑판처럼 region으로 나눠 수거. CMS 대체, 성능 우수 |
💡 CMS는
initial mark → concurrent mark → remark → concurrent sweep단계로 STW 시간을 짧게 가져가지만, compaction이 없어 메모리 단편화가 생길 수 있다. G1GC는 young/old를 한 방식(region)으로 통합 관리해 이를 개선했다.
JNI (Java Native Interface)
Runtime의 gc() 메소드를 보면 native가 붙어 있다. JNI는 C/C++로 작성된 모듈을 자바에서 호출할 수 있게 해주는 기능이다. (MS가 공개한 자바 GC 툴킷도 참고할 만하다.)
📝 정리
1
2
3
4
5
6
Garbage Collection
├─ 목적 Heap의 Unreachable 객체를 자동 회수
├─ 알고리즘 Mark → Sweep → Compact
├─ 세대 Eden → Survivor(Minor GC) → Old(Major GC, STW)
├─ age-bit 살아남을수록 증가 → 임계값 시 Old로 promotion
└─ 정책 Serial / Parallel / CMS / G1GC (성능 트레이드오프)
| 개념 | 한 줄 정의 |
|---|---|
| Reachable/Unreachable | 참조 여부, Unreachable이 수거 대상 |
| Minor/Major GC | Young 수거 / Old 수거(STW) |
| Stop-the-world | GC 중 모든 스레드 정지 |
| G1GC | region 기반의 현대적 GC |
GC는 자바의 편리함을 떠받치는 핵심 메커니즘이다. “Unreachable 객체를 Mark&Sweep으로 수거하고, 세대별로 관리하며, 정책에 따라 성능이 갈린다”는 큰 그림을 잡아두면 실무의 GC 튜닝도 이해하기 쉬워진다.










Comments powered by Disqus.