---
id: wiki-2026-0508-gc-root
title: GC Root
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Garbage Collection Root, GC 루트, Root Set]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [gc, jvm, memory, runtime]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
  language: Java/Kotlin
  framework: JVM/HotSpot/ZGC
---

# GC Root

## 매 한 줄
> **"매 도달 가능성(reachability) 의 출발점"**. GC Root 는 garbage collector 가 live object 를 판별하기 위해 traversal 을 시작하는 reference set. 매 root 에서 도달 불가능한 object 는 collectible 로 marking 된다. JVM, .NET CLR, V8, Go runtime 모두 같은 개념을 공유한다.

## 매 핵심

### 매 GC Root 종류 (HotSpot 기준)
- **Stack reference**: 매 thread 의 local variable / parameter / operand stack.
- **Static field**: 매 loaded class 의 static reference.
- **JNI local/global**: 매 native code 가 hold 한 reference.
- **Active thread**: 매 살아있는 `Thread` object 자체.
- **Class metadata**: 매 ClassLoader, Class object (Metaspace).
- **Synchronization monitor**: 매 `synchronized` 로 lock 된 object.
- **Weak/Soft/Phantom 제외**: 매 reachability 약한 reference 는 root 가 아님.

### 매 Reachability tier
1. **Strongly reachable**: root 에서 strong ref 만으로 도달 → never collected.
2. **Softly reachable**: SoftReference 로만 도달 → 매 memory pressure 시 collect.
3. **Weakly reachable**: WeakReference 만 → 매 next GC cycle 에 collect.
4. **Phantom reachable**: PhantomReference → enqueue 후 cleaning.
5. **Unreachable**: → collectible.

### 매 응용
1. **Memory leak 진단** (heap dump → root path 추적).
2. **Escape analysis** (root 도달 안 되는 local → stack alloc).
3. **Concurrent GC 의 root scanning** (ZGC, Shenandoah, G1 의 phase 1).

## 💻 패턴

### 1. Heap dump 에서 root path 찾기 (Eclipse MAT / jhat)
```bash
# JVM heap dump 생성
jcmd <pid> GC.heap_dump /tmp/heap.hprof

# Eclipse MAT CLI 로 leak suspects 분석
./MemoryAnalyzer -consoleLog -application org.eclipse.mat.api.parse \
  /tmp/heap.hprof org.eclipse.mat.api:suspects
```

### 2. JFR 로 GC root 통계 수집 (JDK 21+)
```bash
java -XX:StartFlightRecording=duration=60s,filename=app.jfr,settings=profile \
     -XX:+UseZGC -Xmx4g -jar app.jar

jfr print --events GarbageCollection,GCReferenceStatistics app.jfr
```

### 3. WeakReference 로 root 진입 회피
```java
import java.lang.ref.WeakReference;
import java.util.WeakHashMap;

// Cache 가 GC root 가 되어 leak 발생하는 anti-pattern 회피
public class Cache<K, V> {
    private final WeakHashMap<K, V> map = new WeakHashMap<>();

    public void put(K key, V value) { map.put(key, value); }
    public V get(K key) { return map.get(key); }
    // key 가 외부에서 strong ref 사라지면 entry 자동 제거
}
```

### 4. ThreadLocal leak 회피 (매 typical root leak)
```java
public class RequestContext {
    private static final ThreadLocal<UserSession> CTX = new ThreadLocal<>();

    public static void set(UserSession s) { CTX.set(s); }
    public static UserSession get() { return CTX.get(); }

    // 매 critical: thread pool 환경에서 반드시 remove() 호출
    public static void clear() { CTX.remove(); }
}

// Servlet filter 에서:
try {
    RequestContext.set(session);
    chain.doFilter(req, res);
} finally {
    RequestContext.clear();  // 매 root reference 끊기
}
```

### 5. JNI global ref 정리 (native code 가 GC root)
```c
// JNI: global ref 는 명시적으로 free 해야 함
jobject globalRef = (*env)->NewGlobalRef(env, localObj);
// ... 사용 ...
(*env)->DeleteGlobalRef(env, globalRef);  // 매 빠뜨리면 영구 leak
```

### 6. Static field 로 의도된 long-lived root
```kotlin
object AppMetrics {  // 매 Kotlin object → static singleton → GC root
    private val counter = AtomicLong(0)
    fun increment() = counter.incrementAndGet()
    fun snapshot() = counter.get()
}
```

### 7. ZGC / Shenandoah concurrent root scanning 활성화
```bash
# JDK 21+: ZGC generational
java -XX:+UseZGC -XX:+ZGenerational \
     -XX:SoftMaxHeapSize=8g -Xmx16g -jar app.jar

# Shenandoah
java -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational -jar app.jar
```

### 8. Async profiler 로 allocation root 찾기
```bash
./profiler.sh -e alloc -d 60 -f alloc.html <pid>
# 매 flamegraph 에서 root → leaking site path 시각화
```

## 매 결정 기준
| 상황 | Approach |
|---|---|
| Long-lived cache | `WeakHashMap` / Caffeine `weakKeys()` |
| Per-request state in pool | `ThreadLocal` + `try/finally remove()` |
| Native handle wrapping | `Cleaner` (java.lang.ref.Cleaner, JDK 9+) |
| Listener registration | `WeakReference` 로 listener wrap |
| 매 short-lived burst alloc | 매 그냥 strong ref + young gen 의존 |

**기본값**: 매 strong ref + escape analysis 신뢰. WeakReference 는 매 진짜 leak 패턴 (cache, listener, ThreadLocal in pool) 에만.

## 🔗 Graph
- 부모: [[Garbage Collection]]
- 응용: [[Memory Leak Detection]]

## 🤖 LLM 활용
**언제**: heap dump 의 leak suspect 해석, root path 의 의미 설명, ThreadLocal/static leak pattern detection.
**언제 X**: 매 production 의 실제 GC tuning (real profiling 데이터 없이 추측 X).

## ❌ 안티패턴
- **Static collection 무한 grow**: `static List` 에 매 add 만 하고 remove 안 함 → 영구 root.
- **ThreadLocal in pool no remove**: 매 thread 재사용 시 이전 request data 가 root 로 남음.
- **JNI GlobalRef leak**: native 에서 `DeleteGlobalRef` 누락.
- **Anonymous inner class capture**: `new Runnable() {}` 가 outer `this` 를 capture → outer 가 root 에 매달림.
- **Listener 등록 후 unregister 안 함**: 매 EventBus, Observer 패턴의 classic leak.

## 🧪 검증 / 중복
- Verified (OpenJDK HotSpot source, JEP 376/439, Eclipse MAT docs).
- 신뢰도 A.

## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — GC root taxonomy + leak 진단 패턴 |
