더 이상 쓸 수 없는 객체의 메모리를 자동으로 회수하는 것.
무엇을 쓰레기로 보나 — 도달성
GC 루트에서 참조를 따라가 '닿을 수 있는가' 로 판단한다
GC 루트
- 실행 중인 스레드의 스택에 있는 지역 변수
- static 필드
- JNI 참조
- 동기화 모니터로 쓰이는 객체
닿지 않으면 쓰레기 — 참조 개수가 아니다
참조 개수 방식이 아니라서 순환 참조도 회수된다.
A a = new A(); B b = new B();
a.ref = b; b.ref = a; // 서로 참조
a = null; b = null; // 루트에서 끊겼다 → 둘 다 회수 대상
왜 세대로 나누나 — 약한 세대 가설
관찰: 대부분의 객체는 만들어지자마자 죽는다
→ 힙을 나눈다
Young 새 객체. 자주·빠르게 청소한다 (Minor GC)
Old 살아남은 객체. 가끔 청소한다 (Major/Full GC)
젊은 세대는 살아남는 게 극소수라, '살아남은 것만 복사' 하면 끝난다
- 쓰레기가 많을수록 오히려 빠르다
할당이 왜 그렇게 빠른가 — TLAB
스레드마다 Eden 의 한 조각을 미리 떼어 준다 (Thread-Local Allocation Buffer)
- 객체 할당 = 포인터를 옆으로 미는 것뿐 (bump-the-pointer)
- 동기화가 필요 없다 (내 영역이니까)
그래서 "작은 객체를 많이 만드는 것" 이 생각보다 싸다
Stop-The-World
객체 그래프를 훑는 동안 애플리케이션 스레드를 멈춰야 하는 구간
이 시간을 줄이는 것이 현대 GC 의 목표다
- 동시 실행(concurrent) · 점진적(incremental) 처리로 STW 구간을 잘게 쪼갠다
어느 GC를 쓰나
- Serial — 단일 스레드. 아주 작은 힙 · 컨테이너 1코어
- Parallel — 처리량 우선. 배치 작업에 적합
- G1 (기본) — 힙을 리전으로 나눠 '쓰레기가 많은 리전부터' 청소
- 목표 정지 시간을 지정할 수 있다 (-XX:MaxGCPauseMillis)
- ZGC — 정지 시간이 힙 크기와 무관하게 sub-millisecond
- 큰 힙 · 낮은 지연이 중요한 서비스. Java 21+ 는 세대별 ZGC
- Shenandoah — ZGC 와 비슷한 목표 (Red Hat)
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 # 기본
-XX:+UseZGC -XX:+ZGenerational # 낮은 지연이 중요할 때
-Xlog:gc*:file=gc.log:time,uptime:filecount=5 # 로그를 남긴다 (필수)
GC 튜닝의 순서
-
① GC 로그를 남기고 실제 정지 시간·빈도를 본다
-
② 대부분의 문제는 GC 설정이 아니라 '너무 많이 만드는 코드' 다
- (불필요한 객체 · 큰 컬렉션 · 문자열 결합 · 박싱)
-
③ 그다음이 힙 크기
-
④ 마지막이 GC 알고리즘 교체
-
순서를 뒤집으면 대개 헛수고한다
면접 함정
- ❌ "참조가 0이면 회수된다" → 참조 카운팅이 아니라 도달성이다.
- ❌ "System.gc()로 청소할 수 있다" → 요청일 뿐 강제가 아니고, 대개 Full GC를 유발해 해롭다.
로그를 읽는 법
[2.456s][info][gc] GC(12) Pause Young (Normal) (G1 Evacuation Pause) 512M->48M(1024M) 12.345ms
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^
종류 원인 GC 전->후(전체 힙) 정지 시간
건강한 신호
- Young GC 가 짧고(수십 ms) 회수량이 크다 (512M → 48M)
- Full GC 가 거의 없다
나쁜 신호
- GC 후에도 사용량이 안 줄어든다 (48M → 480M) → 누수
- Full GC 가 반복된다
- 정지 시간이 초 단위
누수를 잡는 순서
jcmd <pid> GC.heap_info # 현재 상태
jmap -histo:live <pid> | head -20 # 어떤 클래스가 많나 (live 는 Full GC 를 유발한다)
jcmd <pid> GC.heap_dump /tmp/heap.hprof # 덤프 후 MAT·VisualVM 으로 분석
-XX:+HeapDumpOnOutOfMemoryError # OOM 시 자동 덤프 (운영에 반드시 켠다)
HeapDumpOnOutOfMemoryError를 미리 켜 두지 않으면 OOM이 났을 때 원인을 알 방법이 사라진다.
컨테이너에서 주의할 것
-XX:MaxRAMPercentage=75.0 # 컨테이너 메모리의 비율로 힙을 잡는다 (-Xmx 고정보다 유연)
# Java 10+ 는 cgroup 을 인식하지만, 힙 밖(메타스페이스·스레드 스택·다이렉트 버퍼)도
# 컨테이너 한도에 포함되므로 100% 를 주면 OOMKilled 된다