JVM 가비지 컬렉션: 세대별 메모리와 수집기 선택

JVM 가비지 컬렉션의 세대별 Heap 구조, Minor·Full GC 흐름, Serial·Parallel·CMS·G1 수집기와 튜닝 기준을 정리한다.

2026-08-14 · 최초 발행 2025-12-30

참조가 끊긴 객체를 JVM이 회수하는 방식

GC(Garbage Collection)는 JVM이 더 이상 참조되지 않는 객체를 자동으로 메모리에서 제거하는 기능이다. 수동 해제 코드를 줄이고 메모리 누수를 방지하는 것이 핵심이다. 개발자는 메모리 해제 자체보다 비즈니스 로직에 집중할 수 있고, 명시적인 해제 코드가 줄어 코드의 가독성과 유지보수성도 나아진다.

JVM의 Heap은 Young Generation과 Old Generation으로 나뉜다. 새 객체는 Young 영역에서 관리되며, 오래 살아남은 객체는 Old 영역으로 이동한다. 두 영역에서는 Minor GC와 Full GC가 서로 다른 비용과 빈도로 수행된다.

GC 이점JVM개발자사용자자동 메모리 관리누수 방지생산성 향상버그 감소안정성품질 향상고품질애플리케이션

Heap 안에서 객체가 이동하는 경로

Young Generation은 Eden과 두 개의 Survivor 영역으로 구성된다. 새 객체는 Eden에 할당되고, Minor GC 후에도 살아남으면 Survivor 영역으로 옮겨진다. Survivor 0과 Survivor 1은 교대로 사용하며, 일반적인 비율은 Eden : S0 : S1 = 8 : 1 : 1이다.

일정 횟수 이상 살아남은 객체는 Old Generation으로 승격된다. Old Generation은 Tenured Generation이라고도 하며, Young Generation보다 크고 Full GC가 발생하는 영역이다.

Java 7 이하의 Permanent Generation은 클래스 메타데이터와 static 변수를 보관하며 OutOfMemoryError: PermGen space가 발생할 수 있다. Java 8+에서는 이를 Metaspace가 대체한다. Metaspace는 네이티브 메모리를 사용하고 크기를 동적으로 조정한다.

Heap 메모리Young GenerationOld GenerationMetaspace(Java 8+)EdenSurvivor 0Survivor 1 객체할당Minor GC생존자장수 객체Full GC클래스메타데이터

Minor GC는 Eden과 Survivor 영역을 교대시킨다

Minor GC는 Young Generation에서 수행된다. 먼저 Eden에 남아 있는 살아있는 객체를 Survivor 1로 옮기고 Eden을 비운다. 다음 수집에서는 Eden과 Survivor 1의 생존 객체를 Survivor 2로 이동시킨 뒤 두 영역을 정리한다. 이후에는 Survivor 1과 Survivor 2가 같은 방식으로 교대한다.

이 과정을 여러 번 통과한 객체는 Old Generation으로 승격된다. Age 임계값은 기본 15회다. Minor GC는 수 ms ~ 수십 ms 정도로 빠르고 자주 일어나며, Stop-The-World 시간도 짧다.

Eden(새 객체)Minor GCSurvivor 0/1(생존자)반복 생존Old Generation(승격)Age카운터

Full GC가 멈춤 시간을 만드는 이유

Full GC는 전체 객체의 참조를 따라가는 Mark-Sweep-Compact 과정으로 메모리를 회수한다. Mark 단계에서는 GC Root부터 탐색해 Reachable 객체를 표시한다. Sweep 단계는 표시되지 않은 객체를 제거하고, Compact 단계는 남은 객체를 한쪽으로 모아 단편화를 해소하고 연속된 여유 공간을 만든다.

이 수집은 수백 ms ~ 수 초가 걸릴 수 있고, Minor GC보다 드물지만 성능 영향은 크다. 실행 중에는 Stop-The-World가 발생해 모든 애플리케이션 스레드가 중단되고 GC 스레드만 동작한다. 사용자 경험에도 영향을 줄 수 있는 구간이다.

Full GC 시작Stop-The-World(모든 스레드 중지)Mark(Reachable 표시)Sweep(Unreachable 삭제)Compact(메모리 압축)Resume(애플리케이션 재개)

처리량과 지연 시간에 따라 달라지는 수집기

Serial GC는 Young 영역에서 Mark-Sweep, Old 영역에서 Mark-Sweep-Compact를 단일 스레드로 수행한다. 구현이 단순하며 CPU 코어 1개 환경과 수백 MB 규모의 작은 Heap, 클라이언트 애플리케이션에 맞는다.

Parallel GC는 Young 영역을 멀티 스레드로 수집하고 Old 영역에는 Mark-Sweep-Compact를 사용한다. 처리량을 중시하고 멀티 CPU를 활용하는 방식이며, Java 8 default다. 배치 처리나 처리량이 중요한 서버에 적합하다.

Parallel Compacting GC는 Parallel GC를 확장해 Old Generation도 병렬로 Mark-Sweep-Compaction 처리한다. 그만큼 Full GC를 더 빠르게 수행한다.

CMS(Concurrent Mark & Sweep) GC는 Young 영역에 병렬 콜렉터를 쓰고, Old 영역에서 Mark → Sweep → Remark → Concurrent Sweep 순서로 동작한다. Concurrent Phase가 애플리케이션과 함께 실행돼 Stop-The-World 시간을 최소화한다. 낮은 지연 시간이 필요한 웹 애플리케이션과 응답 시간 중심 서버에서 사용할 수 있지만, Compact를 수행하지 않아 단편화가 생길 수 있고 CPU 사용률이 높다.

G1(Garbage First) GC는 Heap을 격자 형태의 Region으로 나누고 Eden, Survivor, Old 영역을 동적으로 할당한다. 우선순위 기반으로 수집하며 수 GB ~ 수십 GB의 대용량 Heap에서 예측 가능한 Stop-The-World 시간을 목표로 한다. Heap 크기에 관계없이 일정한 지연 시간을 제공하고 Compact를 자동 수행해 단편화를 줄인다. Java 9+ default이며, 대용량 Heap 서버와 실시간성이 중요한 애플리케이션에 맞는다.

GC 알고리즘Serial GCParallel GCCMS GCG1 GC단일 스레드작은 Heap처리량 중시멀티 CPU낮은 지연응답 시간대용량 Heap예측 가능

튜닝은 목표와 로그를 함께 본다

GC 튜닝에서는 GC 발생 빈도, GC 소요 시간, Stop-The-World 시간, 처리량(Throughput)을 먼저 본다. 원본 기준 목표는 Minor GC 50ms 이하, Full GC 1초 이하, Full GC 빈도 시간당 1회 이하이다.

Heap 크기는 다음 파라미터로 제어한다.

  • -Xms: 초기 Heap 크기
  • -Xmx: 최대 Heap 크기

두 값은 일반적으로 동일하게 설정해 성능을 높인다. Young과 Old 비율은 -XX:NewRatio로 정하며 기본값은 2다. Eden과 Survivor 비율은 -XX:SurvivorRatio로 조정하고 기본값은 8이다.

Heap Size는 서버 메모리의 1/2 ~ 2/3 범위에서 고려한다. 너무 크면 Full GC 시간이 증가하고, 너무 작으면 GC가 빈번해진다. Java 8+의 Metaspace는 클래스 수에 맞춰 -XX:MetaspaceSize-XX:MaxMetaspaceSize를 조정한다.

로그를 남기려면 다음 옵션을 사용한다.

  • -XX:+PrintGCDetails
  • -XX:+PrintGCDateStamps
  • -Xloggc:gc.log

GCEasy, GCViewer, VisualVM, JConsole로 로그와 JVM 상태를 분석할 수 있다. 수집기 선택과 Heap 설정은 처리량 및 지연 시간의 트레이드오프를 기준으로 잡고, 모니터링 결과에 따라 계속 조정한다.

JVM가비지 컬렉션자바메모리 관리GC 튜닝