Files
2nd/10_Wiki/Topics/Programming & Language/GC Root.md
T
Antigravity Agent f8b21af4be Wiki cleanup: error-doc removal, dedup merge, link normalization
10_Wiki/Topics 대규모 정리:
- 오류 캡처/미완성 stub 문서 227개 제거
- 교차폴더 중복 43클러스터 병합 (63파일 → redirect)
- 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건
- 카테고리 MOC 6개 신규 생성
- Graph 섹션 미해결 related-keyword 링크 10,058건 제거

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:52:15 +09:00

173 lines
6.0 KiB
Markdown

---
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 진단 패턴 |