G1 GC의 구조적 특징과 대규모 트래픽 시스템에서의 튜닝 포인트

지난 포스팅에서는 응답 속도를 높이려다 메모리 단편화라는 치명적인 단점을 안고 역사 속으로 사라진 CMS GC에 대해 알아보았습니다. 구글, 넷플릭스 등 대규모 트래픽을 처리하는 글로벌 IT 기업들은 거대한 힙(Heap) 메모리를 사용하면서도 시스템이 멈추는 시간(Stop-The-World)을 예측하고 제어할 수 있는 새로운 가비지 컬렉터가 필요했습니다.

그렇게 탄생하여 Java 9부터 공식적인 디폴트(기본) GC로 채택된 것이 바로 G1 GC(Garbage First GC)입니다. 이번 글에서는 기존의 틀을 완전히 깨버린 G1 GC의 혁신적인 구조와, 실무 운영 환경에서 성능을 극대화하기 위한 핵심 튜닝 포인트를 분석해 보겠습니다.

1. 기존 메모리 구조의 판을 엎다: 리전(Region) 기반 아키텍처

기존의 GC(Serial, Parallel, CMS)들은 힙 메모리를 물리적으로 큼직하게 나누어 Young Generation과 Old Generation으로 고정 배치했습니다. 하지만 G1 GC는 이 고정관념을 버리고 힙 메모리를 바둑판처럼 잘게 쪼개는 리전(Region)이라는 개념을 도입했습니다.

  • 동적 역할 부여: 전체 힙을 1MB ~ 32MB 크기의 동일한 리전 수천 개로 나눕니다. 그리고 각 리전은 상황에 따라 동적으로 Eden, Survivor, Old 영역의 역할을 부여받습니다.
  • Humongous 영역의 등장: G1 GC에는 기존에 없던 새로운 역할이 추가되었습니다. 하나의 리전 크기의 50%를 초과하는 매우 큰 객체(거대 객체)가 생성되면, 이를 연속된 리전에 할당하고 Humongous(거대) 리전으로 특별 관리합니다.

2. 왜 이름이 ‘Garbage First(G1)’일까?

G1 GC는 메모리 전체를 한 번에 청소하려고 하지 않습니다. 각 리전마다 살아있는 객체와 쓰레기(Unreachable) 객체의 비율을 백그라운드 스레드가 실시간으로 추적합니다.

청소가 시작되면, 이름 그대로 ‘쓰레기가 가장 많은(Garbage First) 리전’을 우선순위로 선택하여 집중적으로 청소합니다. 가비지가 꽉 찬 리전을 먼저 비워내기 때문에, 짧은 시간 안에 가장 효율적으로 많은 여유 메모리 공간을 확보할 수 있습니다.

  • 메모리 단편화 해결: CMS GC의 가장 큰 약점이었던 파편화 문제도 해결했습니다. G1 GC는 쓰레기가 많은 리전에서 살아남은 소수의 객체들을 완전히 비어있는 새로운 리전으로 차곡차곡 ‘복사(Copying)’하여 옮깁니다. 이 과정에서 자연스럽게 압축(Compaction)이 일어나 메모리 파편화가 방지됩니다.

3. 목표 정지 시간(Pause Time Goal) 설정

G1 GC의 가장 강력한 무기는 개발자가 “GC로 인해 애플리케이션이 멈추는 시간을 설정한 목표치 이내로 유지해 줘”라고 명령할 수 있다는 점입니다.

이를 ‘예측 가능한 정지 시간 모델(Predictable Pause Time Model)’이라고 부릅니다. 개발자가 목표 시간(예: 200ms)을 설정하면, JVM은 과거의 GC 소요 시간 데이터를 분석하여 다음 번 GC 때 몇 개의 리전을 청소해야 목표 시간을 맞출 수 있을지 스스로 계산하고 청소 양을 조절합니다.

4. 대규모 시스템에서의 G1 GC 실무 튜닝 포인트

G1 GC는 자체적으로 목표 시간에 맞춰 최적화(Auto-tuning)를 수행하므로, 과거처럼 복잡한 옵션을 여러 개 건드리는 것은 오히려 성능을 저하시킬 수 있습니다. 실무에서는 아래의 핵심 옵션 위주로 튜닝을 진행합니다.

  • -XX:MaxGCPauseMillis: 앞서 설명한 ‘최대 GC 허용 정지 시간’을 설정합니다. (기본값은 보통 200ms입니다.) 주의할 점은 이 값을 너무 낮게(예: 50ms) 설정하면, 한 번에 청소할 수 있는 리전의 수가 적어져 GC가 너무 빈번하게 발생하고 전체 처리량(Throughput)이 급격히 떨어지게 됩니다. 시스템 요구사항과 타협점을 찾는 것이 핵심입니다.
  • -XX:G1HeapRegionSize: 리전의 크기를 명시적으로 지정합니다. 애플리케이션에서 크기가 큰 객체를 자주 생성하여 Humongous 영역 할당이 너무 빈번하게 일어난다면(이는 잦은 Full GC를 유발합니다), 리전 사이즈를 늘려 Humongous 객체 생성을 억제하는 튜닝이 필요합니다.

결론: 응답성과 처리량의 완벽한 밸런스

G1 GC는 대용량 힙 메모리를 사용하면서도 Stop-The-World 시간을 예측 가능하게 만들어, 트래픽이 몰리는 웹 서비스에서 ‘응답 속도’와 ‘전체 처리량’ 사이의 황금 밸런스를 잡아낸 훌륭한 가비지 컬렉터입니다. Java 8 환경의 시스템들이 Java 11이나 17로 마이그레이션 할 때 얻을 수 있는 가장 큰 성능적 이점 중 하나가 바로 이 G1 GC의 기본 적용입니다.

하지만 기술의 발전은 멈추지 않습니다. 다음 포스팅에서는 수백 기가바이트(GB)에서 테라바이트(TB) 단위의 힙 메모리에서도 정지 시간을 10ms 이하로 보장하는 차세대 가비지 컬렉터, ‘ZGC 및 Shenandoah GC: 차세대 초저지연 가비지 컬렉터 집중 분석’을 살펴보겠습니다.

댓글 남기기