--- 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 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 { private final WeakHashMap 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 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 # 매 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 진단 패턴 |