본문 바로가기
Language & Framework

Java GC의 역사와 진화: STW에서 동시 수집으로

by Jordy-torvalds 2025. 5. 17.

1. 자바 GC의 발전 흐름

Java의 GC(Garbage Collection)는 프로그램 내에서 사용되지 않는 객체를 자동으로 정리해주는 메모리 관리 메커니즘이다. 초기 GC는 단순하지만 응답 지연이 길었고, 이후 다양한 GC 알고리즘들이 등장하면서 처리량(Throughput), 지연시간(Latency), 오버헤드(Overhead)의 균형을 지향하게 되었다.

발전 흐름 요약

Java의 GC는 초창기부터 Young / Old 세대를 구분하는 Generational GC 구조를 갖고 있었으며, Serial GC와 Parallel GC에서도 이 구조는 동일하게 적용되었다. 이후 응답 지연(latency)을 줄이기 위한 병행 수집(concurrent collection) 전략이 CMS부터 도입되었고, G1에서는 region 단위 수집과 pause time 예측이 가능한 구조로 발전했으며, ZGC에 이르러서는 초저지연을 목표로 대부분의 작업이 병행 처리되는 구조로 진화했다.

  1. 초기 GC: Generational 구조 기반의 STW GC (Serial, Parallel GC)
  2. 동시 수집 전략의 도입: CMS (Old 영역의 Concurrent 수집)
  3. Pause Time 제어와 Region 기반 GC: G1 GC
  4. 초저지연과 완전 병행 수집: ZGC, Shenandoah
  5. 병행 수집 + 세대 구분 강화: Generational ZGC

2. GC의 일반적인 처리 과정

GC는 일반적으로 다음과 같은 단계를 따른다:

Mark → Sweep → (Compact)

  • Mark: 루트에서부터 도달 가능한 객체를 식별
  • Sweep: 가비지로 판단된 객체를 제거
  • Compaction: 메모리 단편화를 줄이기 위해 객체 재배치 (선택적)

특정 GC에 따라 일부 단계는 생략되거나 순서가 다를 수 있다. 예를 들어 CMS는 compaction을 수행하지 않기 때문에 memory fragmentation 문제가 있다.


3. 주요 GC 알고리즘 비교

3.1 CMS (Concurrent Mark-Sweep)

  • Old Generation만 병행 수집
  • Stop-The-World로 Initial Mark, Remark 수행
  • Compaction 없음 → fragmentation 발생
  • 이미 Deprecated 되었지만 레거시 시스템에 여전히 사용됨

3.2 G1 GC

  • 힙을 수천 개의 Region으로 나누어 관리
  • 세대 구분 존재 (Young / Old)
  • Young GC는 STW, Old GC는 병행 수집
  • Region 단위로 수집하여 pause time 제어
  • Partial Compaction 수행 가능
  • -XX:MaxGCPauseMillis로 목표 pause 설정 가능
  • 버전
    • Java 7u4: production ready(이전에는 experiment)
    • Java 9: default gc(이전에는parallel gc)

3.3 ZGC

  • 모든 주요 단계가 병행(concurrent)
  • Stop-The-World 구간은 1~2ms 수준
  • Color Pointer + Load Barrier를 활용하여 이동 중인 객체 접근 보정
  • 메모리 복제(Copying) 기반 GC
  • Java 17까지는 세대 구분 없음
  • 버전
    • Java 15 experimental 해제 및 production ready
    • G1GC와 달리 기본 GC로 채택되지 못함
-XX:+UseZGC

3.4 Generational ZGC

  • Java 21부터 도입된 ZGC의 개선 버전
  • Young / Old 세대 구분 도입
  • Young 영역은 짧은 주기로 빠르게 수집
  • Old 영역은 덜 자주 수집하며 필요한 경우만 이동
  • 객체 churn이 큰 워크로드에서 효율적
-XX:+UseZGC -XX:+ZGenerational

4. GC 성능 지표: Throughput vs Latency vs Overhead

GC는 단순히 빠르다고 좋은 것이 아니다. 어떤 GC를 선택하고 튜닝하느냐는 다음 세 가지 관점에서 결정된다.

4.1 Throughput (처리량)

  • 애플리케이션이 GC로 중단되지 않고 실제로 처리한 시간의 비율
  • 높을수록 전체 시스템 처리 능력이 뛰어남
  • Throughput = Application Time / (Application Time + GC Time)
  • 배치 시스템, 로그 처리, 데이터 수집기 등에서는 중요

4.2 Latency (지연시간)

  • GC에 의해 애플리케이션 응답이 지연된 시간
  • 짧은 pause와 낮은 jitter가 중요
  • 금융 시스템, 실시간 API, 게임 서버 등에서는 치명적 요소

4.3 Overhead (자원 오버헤드)

  • GC가 동작하기 위해 사용하는 CPU, 메모리, barrier, thread 등 자원
  • 병행 GC는 낮은 latency를 위해 많은 background thread와 CPU를 사용
  • 시스템 자원이 제한된 환경에서는 고려 필요

5. G1 GC vs ZGC 비교 분석

항목 G1 GC ZGC
세대 구분 있음 없음(17까지), 있음(21부터)
수집 단위 Region Heap Page 기반
Young 수집 STW 병행 또는 부분 STW
Old 수집 병행 (Concurrent Mark & Sweep) 병행 (Mark / Relocate / Remap)
Pause Time 수십~수백 ms 1~2ms 고정
Barrier 사용 Card Table 기반 Write Barrier Load Barrier (Color Pointer 기반)
오버헤드 중간 큼 (높은 CPU + 메모리 요구)
Throughput 일반적으로 높음 낮을 수 있음
코어 수 의존도 적당 높음
적합 케이스 범용 서버, 웹, 백엔드 초저지연 실시간 시스템, 대용량 힙 환경

6. 성능 특성에 따른 GC 선택 가이드

GC 유형 Latency Throughput Overhead 권장 시나리오
Serial 높음 중간 낮음 테스트, 소형 앱
Parallel 높음 매우 높음 중간 배치 처리, 비동기 서버
CMS 중간 중간 중간 레거시 시스템
G1 GC 중간 높음 중간 일반적인 서버, 예측 가능한 응답 시간
ZGC 매우 낮음 중간 이하 높음 금융, 게임, 실시간 응답 필수 시스템
Generational ZGC 매우 낮음 중간 높음 고객 요청이 많고 객체 churn이 높은 API 서비스

결론

GC의 선택은 단순히 “pause time이 짧다”가 아니라, 시스템 자원 조건(CPU, Memory), 응답 시간 요구, 객체 수명 특성을 모두 고려한 전략적인 판단이 필요하다.

  • G1 GC는 대부분의 서비스에서 안정성과 throughput 간 균형을 잘 유지하며,
  • ZGC는 리소스가 넉넉하고 latency가 절대적으로 중요한 시스템에서 최고의 선택이 된다.

현대의 GC는 단순한 메모리 회수가 아닌, 시스템 아키텍처 전체 설계의 일환으로 접근해야 한다.