"매 도달 가능성(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
Strongly reachable: root 에서 strong ref 만으로 도달 → never collected.
Softly reachable: SoftReference 로만 도달 → 매 memory pressure 시 collect.
Weakly reachable: WeakReference 만 → 매 next GC cycle 에 collect.
Phantom reachable: PhantomReference → enqueue 후 cleaning.
Unreachable: → collectible.
매 응용
Memory leak 진단 (heap dump → root path 추적).
Escape analysis (root 도달 안 되는 local → stack alloc).
Concurrent GC 의 root scanning (ZGC, Shenandoah, G1 의 phase 1).
💻 패턴
1. Heap dump 에서 root path 찾기 (Eclipse MAT / jhat)
# 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
importjava.lang.ref.WeakReference;importjava.util.WeakHashMap;// Cache 가 GC root 가 되어 leak 발생하는 anti-pattern 회피publicclassCache<K,V>{privatefinalWeakHashMap<K,V>map=newWeakHashMap<>();publicvoidput(Kkey,Vvalue){map.put(key,value);}publicVget(Kkey){returnmap.get(key);}// key 가 외부에서 strong ref 사라지면 entry 자동 제거}
4. ThreadLocal leak 회피 (매 typical root leak)
publicclassRequestContext{privatestaticfinalThreadLocal<UserSession>CTX=newThreadLocal<>();publicstaticvoidset(UserSessions){CTX.set(s);}publicstaticUserSessionget(){returnCTX.get();}// 매 critical: thread pool 환경에서 반드시 remove() 호출publicstaticvoidclear(){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)
// JNI: global ref 는 명시적으로 free 해야 함
jobjectglobalRef=(*env)->NewGlobalRef(env,localObj);// ... 사용 ...
(*env)->DeleteGlobalRef(env,globalRef);// 매 빠뜨리면 영구 leak
./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).