[JAVA] 공식문서로 공부하는 Garbage Collection - Ch 7. Garbage-First (G1) Garbage Collector(1)

예름·2025년 4월 2일

Java

목록 보기
8/9
post-thumbnail

Oracle의 공식문서 HotSpot Virtual Machine Garbage Collection Tuning Guide을 참고했습니다.

📍 Introduction to Garbage-First (G1) Garbage Collector
📍 Enabling G1
📍 Basic Concepts

📍 개요

현재 대부분의 jvm에서 사용하는 Garbage-First (G1) Garbage Collector의 개념과 과정에 대해 알아보자.


🔎 Introduction to Garbage-First (G1) Garbage Collector

✅ G1 Garbage Collector

Garbage-First(G1) 가비지 컬렉터는 다중 프로세서 환경과 대용량 메모리 시스템을 대상으로 설계되었다.

G1은 가비지 컬렉션(GC) 일시 중지 시간을 최대한 일정하게 유지하면서 높은 처리량을 제공하도록 설계되었으며, 최소한의 설정으로도 효율적으로 동작한다.

❓ 일시 중지 시간을 최대한 일정하게 유지?

일시 중지 시간을 일정하게 유지한다는 뜻:

• 게임을 하다가 갑자기 3초 동안 멈춘다면? 🎮❌
• 웹사이트에서 버튼을 눌렀는데 5초 동안 반응이 없다면? 🖱️❌

➡️ 이런 긴 멈춤을 방지하고, 가능한 한 일정한 간격(예: 100ms 내외)으로 짧게 유지

❓ 높은 처리량을 제공?

높은 처리량을 제공한다는 뜻:

메모리를 관리하는 시간(GC 시간)보다 실제 작업을 수행하는 시간(처리량)이 더 중요

✅ 필요할 때만 메모리를 정리하고
✅ 멀티코어를 활용해서 여러 작업을 병렬로 처리하며
✅ CPU를 너무 많이 쓰지 않게 조절하면서

➡️ 애플리케이션이 최대한 빠르게 실행될 수 있도록 도와줌

✅ G1 Garbage Collector의 주요 특징

1. 대용량 메모리 환경에서 효과적!

➡️ 수십 GB 이상의 큰 메모리를 사용하는 앱에서도 잘 동작

2. 빠르고 효율적인 메모리 정리

➡️ 자주 사용되지 않는 부분부터 정리해서 속도를 최적화

3. 메모리 단편화 해결

➡️ 데이터를 정리할 때 조각난 메모리를 모아서 효율적으로 재사용

4. 일시 중지 시간을 최소화

➡️ GC가 실행될 때 앱이 잠깐 멈추는데, G1은 그 시간을 수백 밀리초 이내로 짧게 유지해서 사용자 경험을 좋게 만듦

5. 앱 실행 중에도 일부 GC 작업 수행

➡️ 다른 GC 방식과 달리, 앱이 실행되는 동안에도 일부 정리를 수행해서 갑자기 오래 멈추는 걸 방지

G1은 여러 개의 가비지 컬렉션 스레드를 사용하여 애플리케이션과 동시에 일부 작업을 수행한다.

따라서 이전의 처리량 중심 수집기(Throughput Collector)보다 GC 일시 중지 시간이 짧아지는 대신 애플리케이션의 전체 처리량은 다소 감소할 수 있다.


🔎 Enabling G1

G1은 기본적으로 활성화되어 있으며, 명령줄에서 -XX:+UseG1GC 옵션을 사용하여 명시적으로 설정할 수도 있다.


🔎 Basic Concepts

✅ 기본 개념

G1은 세대별(Generational), 점진적(Incremental), 병렬(Parallel), 대부분 동시(Concurrent), 일시 정지(Stop-the-World), 이동식(Evacuating) 가비지 컬렉터이며, 일시 정지 시간 목표를 실시간으로 모니터링한다.

다른 GC와 마찬가지로 G1은 힙을 젊은 세대(Young Generation)와 노년 세대(Old Generation)로 나누고, 젊은 세대에서 주로 메모리를 회수하며 필요할 때 노년 세대도 정리한다.

✔ 세대별(Generational)

👉 힙을 젊은 세대(Young Generation)와 노년 세대(Old Generation)로 나눠서 관리

  • 젊은 세대: 새로 생성된 객체가 저장됨 (자주 GC 실행)
  • 노년 세대: 오래된 객체가 저장됨 (필요할 때만 GC 실행)

✔ 점진적(Incremental)

👉 한 번에 모든 메모리를 정리하는 게 아니라, 조금씩 나눠서 정리

✔ 병렬(Parallel)

👉 여러 개의 GC 스레드를 동시에 사용해서 빠르게 정리

✔ 대부분 동시(Concurrent)

👉 애플리케이션이 실행되는 동안에도 일부 GC 작업을 병렬로 수행

  • 예를 들어, 어떤 객체가 필요 없는지 검사하는 작업(마킹 작업)을 애플리케이션과 동시에 진행할 수 있음

✔ 일시 정지(Stop-the-World, STW) 발생

👉 GC가 객체를 실제로 정리할 때는 애플리케이션을 잠깐 멈춰야 하지만(Stop-the-World, STW) 이 멈추는 시간이 최대한 짧아지도록 조정함

✔ 이동식(Evacuating) 방식 사용

👉 메모리를 정리할 때, 살아있는 객체를 새로운 영역으로 이동시키고, 이전 메모리는 깨끗이 비워서 재사용 가능하도록 만듦

  • 이렇게 하면 메모리 단편화 문제도 해결됨!

✅ Heap Layout

G1은 힙을 동일한 크기의 여러 개의 힙 영역(Heap Region)으로 나눈다.

각 힙 영역은 연속된 가상 메모리 공간으로 구성되며, 개별 영역이 메모리 할당 및 회수 단위로 사용된다.

각 영역은 다음과 같은 역할을 한다.

  • 비어있는 영역 (Empty, 연한 회색)
  • 젊은 세대 영역 (Young Generation)
    • Eden(에덴) 영역 (빨간색): 새 객체가 할당되는 공간
    • Survivor(생존) 영역 (빨간색 ‘S’): 살아남은 객체가 머무르는 공간
  • 노년 세대 영역 (Old Generation)
    • 일반 Old 영역 (연한 파란색)
    • 거대한(Humongous) 객체 영역 (연한 파란색 ‘H’): 여러 개의 영역을 차지하는 큰 객체 저장

애플리케이션은 기본적으로 Eden 영역에서 객체를 할당하지만, 크기가 매우 큰 객체는 바로 Old Generation으로 할당될 수 있다.

✅ Garbage Collection Cycle

G1의 GC 주기는 크게 두 가지 단계로 나뉜다.

1. 젊은 세대만 처리하는 단계 (Young-Only Phase)

  • 젊은 세대의 Eden 영역이 가득 차면 가비지 컬렉션이 발생하며 일부 객체가 Old Generation으로 승격됨
  • Old Generation의 점유율이 특정 임계값(초기 힙 점유 임계값, Initiating Heap Occupancy Threshold)을 넘으면 동시 마킹(Concurrent Marking) 단계 시작

2. 공간 회수 단계 (Space-Reclamation Phase)

  • 동시 마킹이 끝나면 혼합(Mixed) 가비지 컬렉션 수행 (젊은 세대 + 일부 Old Generation 영역 정리)
  • Old Generation에서 가비지가 많은 영역을 우선적으로 정리
  • 더 이상 효율적으로 정리할 수 없다고 판단되면 다시 Young-Only Phase로 전환

1. Young-only 단계 (젊은 세대만 수집하는 단계)

이 단계에서는 Eden과 Survivor 영역에서만 가비지 컬렉션을 수행하며, 살아남은 객체들은 Old 영역으로 이동함

✅ Young-only 단계에서 Space-reclamation 단계로 넘어가는 시점

  • Old 영역에 객체들이 점점 쌓이면, 어느 순간 특정 임계치(= Initiating Heap Occupancy Threshold)를 넘게 됨
  • 이 임계치를 넘으면 G1은 다음 Young 컬렉션을 “Concurrent Start” 컬렉션으로 바꿈

2. Concurrent Start (동시 시작)

Concurrent Star는 일반 Young GC처럼 동작하면서 동시에 마킹(Marking) 작업을 시작하는 특별한 Young GC

마킹(Marking)이란?

  • Old 영역에서 살아있는 객체들을 확인하는 작업
  • 살아있는 객체는 다음 단계에서 유지되고, 그렇지 않은 객체는 제거될 예정
  • 하지만 마킹이 끝나려면 시간이 걸리므로, 이 과정에서 일반적인 Young GC가 계속 진행될 수도 있음

🚨 Concurrent Start 중 마킹을 취소하는 경우

  • 만약 G1이 “굳이 마킹을 할 필요가 없다”고 판단하면, 마킹을 취소하고 다시 Young-only 단계로 돌아감
  • 이 경우에는 추가적인 단계(= Remark, Cleanup)가 실행되지 않음

3. Remark 단계

Concurrent Start를 통해 마킹이 끝나면, Remark(재확인) 단계가 실행됨

이 단계에서는 마킹 결과를 확정함과 동시에 다음과 같은 작업이 수행됨

  • 참조 처리 (Reference Processing)
  • 클래스 언로딩 (사용되지 않는 클래스 제거)
  • 완전히 비어진 영역(Region) 해제
  • 내부 데이터 구조 정리

4. Cleanup 단계

Cleanup 단계에서는 마킹된 정보를 바탕으로 Space-reclamation 단계로 넘어갈지 여부를 결정함

  • Space-reclamation으로 넘어가는 경우
    • Young-only 단계는 Prepare Mixed young collection이라는 특별한 Young GC를 수행한 후 종료
  • 그렇지 않은 경우
    • 다시 Young-only 단계로 돌아감

5. Space-reclamation 단계 (공간 회수 단계)

이제 Young GC뿐만 아니라 일부 Old 영역도 함께 정리하는 Mixed GC가 실행됨
이 과정에서 살아있는 객체들은 새로운 Old 영역으로 이동하고, 쓸모없는 객체가 많아진 Old 영역은 해제됨

하지만 이 과정도 무한정 반복되지는 않음
• G1은 “이제 Old 영역을 더 수집해도 효율이 없다!”고 판단하면 Space-reclamation을 종료

비상 상황: Full GC (전체 가비지 컬렉션)

Space-reclamation 과정에서 메모리가 부족하면 어떻게 될까?

  • G1은 최후의 방법으로 Full GC를 실행함
  • Full GC는 G1이 아닌 기존의 전통적인 GC 방식처럼 모든 객체를 정리하면서 힙을 압축(Compaction)하는 방식으로 실행됨
  • 이 과정은 멈춤 시간이 길어질 수 있기 때문에 최후의 수단으로만 사용

정리

  1. Young-only 단계: Young 영역만 정리 → Old 영역 객체가 늘어나면서 Concurrent Start 실행
  2. Concurrent Start: 마킹(Old 영역에서 살아있는 객체 찾기) 시작
  3. Remark: 마킹된 정보 확정 & 불필요한 영역 정리
  4. Cleanup: Space-reclamation 진행 여부 결정
  5. Space-reclamation 단계: Mixed GC 실행 (Young + 일부 Old 영역 정리)
  6. 다시 Young-only 단계로 복귀 (주기가 반복됨)
  7. 메모리가 부족하면 Full GC 실행 (최후의 수단)

✅ Garbage Collection Pauses and Collection Set

1. G1 GC의 가비지 컬렉션 방식

G1 GC는 Stop-the-World(STW) 방식으로 가비지 컬렉션을 수행함
즉, 가비지 컬렉션이 실행되면 애플리케이션이 일시적으로 멈추고, G1이 메모리를 정리한 후 다시 실행됨

🚨 Stop-the-World (STW)
JVM이 가비지 컬렉션을 실행할 때 애플리케이션의 실행을 잠시 중단하는 현상.

G1은 라이브(live) 객체를 소스 영역(Source Region)에서 대상 영역(Destination Region)으로 복사하여 정리함
즉, 살아있는 객체는 다른 영역으로 이동하고, 필요 없는 객체는 버려진다는 뜻

2. 객체가 이동하는 방식

객체가 이동하는 방식은 객체가 현재 위치한 메모리 영역(Region)에 따라 결정됨

✅ 일반 객체 (non-humongous objects)

  • Young(젊은) 세대 객체
    • Eden → Survivor 또는 Old로 이동
    • Survivor → Old로 이동 (객체 나이가 충분하면)
  • Old(오래된) 세대 객체
    • Old → 다른 Old 영역으로 이동 (공간을 최적화하기 위해)

즉, G1은 객체를 복사하면서 불필요한 객체를 자동으로 제거하고, 메모리를 효율적으로 재배치함

✅ 매우 큰 객체 (Humongous objects)

  • Humongous 객체(Region 크기의 절반 이상을 차지하는 큰 객체)는 이동하지 않음
  • G1은 Humongous 객체의 생존 여부만 확인한 후, 더 이상 사용되지 않으면 해당 공간을 회수(reclaim)함
  • 만약 강제 이동이 필요하다면, Full GC에서 최후의 방법으로 이동하지만 매우 느리기 때문에 지양됨

3. Remembered Set과 Collection Set

Remembered Set (기억된 집합)

G1 GC는 가비지 컬렉션 시 객체를 이동시키는데, 이동 후 기존 객체를 참조하는 곳을 새로운 위치로 업데이트해야 함
이 작업을 효율적으로 수행하기 위해 Remembered Set(기억된 집합)이라는 구조를 사용함

✅ Remembered Set이 하는 일

  • 컬렉션 대상(= Collection Set)에 속하지 않는 객체들이 컬렉션 대상 객체를 참조할 경우 추적
  • 객체 이동 시 참조를 업데이트할 위치를 빠르게 찾을 수 있도록 함

즉, G1이 가비지 컬렉션을 수행할 때 다른 곳에서 Collection Set 내부 객체를 참조하고 있는 위치들을 미리 저장해두는 것이 Remembered Set의 역할

✅ Remembered Set의 구조

  • G1은 힙(Heap)을 512바이트 단위의 카드(Card)로 논리적으로 나눠서 관리
  • Remembered Set은 특정 객체가 위치한 카드(Card)를 인덱스로 저장하여 공간을 절약
  • 각 Region(메모리 영역)은 Per-Region Remembered Set을 가짐

즉, 각 메모리 영역마다 해당 영역을 참조하는 외부 객체의 위치를 저장하는 Remembered Set을 관리하고, 가비지 컬렉션 시 이를 이용해 참조를 빠르게 업데이트하는 방식

Collection Set (수집 집합)

Collection Set은 가비지 컬렉션을 수행할 메모리 영역(Region)들의 집합
즉, 이번 GC에서 정리할 대상이 되는 메모리 영역들의 목록

✅ Collection Set에 포함되는 영역들

  • 가비지가 많아 회수할 가치가 높은 영역을 우선적으로 포함
  • Humongous 객체는 특별 처리(불필요하면 회수, 이동은 최소화)
  • Old 영역도 필요하면 포함하지만, 효율성이 낮으면 제외

이 Collection Set은 가비지 컬렉션을 진행하기 전에 선정되며, 특히 Remark Pause 단계에서 효율적으로 정리할 수 있는 영역을 미리 선정하는 것이 중요함

Garbage Collection Process (가비지 컬렉션 과정)

1️⃣ Pre Evacuate Collection Set (사전 준비)

  • 가비지 컬렉션을 위한 준비 작업 수행
  • TLAB(Thread Local Allocation Buffer)를 분리
  • 각 스레드가 가지고 있던 작은 메모리 버퍼(TLAB)를 해제
  • Collection Set 선정
  • 이번 가비지 컬렉션에서 정리할 메모리 영역 결정
  • 기타 사전 작업 수행

📌 TLAB이란?

  • 각 스레드가 할당 속도를 높이기 위해 사용하는 작은 메모리 영역
  • 가비지 컬렉션을 수행하기 전, 기존 할당된 TLAB을 해제하는 작업 필요

2️⃣ Merge Heap Roots (루트 합치기)

  • 여러 개의 Remembered Set을 하나로 통합하여 중복 제거
  • 컬렉션 대상(= Collection Set)으로부터의 참조를 빠르게 찾기 위해 정리

이 과정이 중요한 이유는 가비지 컬렉션을 병렬로 수행할 때 속도를 높일 수 있기 때문
여러 개의 Remembered Set을 하나로 합쳐 놓으면, 객체 이동 후 참조 업데이트하는 작업이 훨씬 빨라짐

3️⃣ Evacuate Collection Set (객체 이동 및 정리)

  • 가비지 컬렉션의 핵심 단계!
  • 루트(Roots)에서 시작하여 살아있는 객체를 새로운 위치로 이동
  • 객체를 복사하면서 참조를 업데이트 (Remembered Set을 이용)

📌 루트(Root)란?

  • JVM 내부의 데이터 구조 (예: 스택, 정적 변수 등)에서 직접 참조하는 객체
  • 코드에서 직접 참조하는 객체
  • Heap 내부에서 다른 객체를 참조하는 객체

📌 과정
1. 루트에서 시작하여 살아있는 객체를 탐색
2. 살아있는 객체를 새로운 위치로 복사
3. 복사된 객체의 참조를 업데이트
4. 모든 루트를 처리할 때까지 반복

이 과정에서 Humongous 객체는 특별히 다루는데, 이동시키지 않고 필요할 경우에만 제거하는 방식으로 처리함

4️⃣ Post Evacuate Collection Set (후처리)

  • 가비지 컬렉션이 끝난 후의 정리 작업 수행
  • 참조 처리 및 다음 Mutator Phase(애플리케이션 실행) 준비

즉, 객체 복사가 끝난 후의 후처리 과정
이 과정이 끝나면 가비지 컬렉션이 마무리되고, 애플리케이션이 다시 실행됨

정리

✔ Remembered Set

  • G1이 객체 이동 후 참조를 업데이트하기 위해 유지하는 정보
  • 각 Region(메모리 영역)에 대한 참조 정보를 저장

✔ Collection Set

  • 이번 가비지 컬렉션에서 정리할 메모리 영역들의 목록
  • Young 영역, Humongous 객체, 필요시 Old 영역 포함
  • 불필요한 데이터가 많고, 회수할 가치가 높은 영역이 우선 대상

정리

✔ Garbage Collection Process (4단계)
1. Pre Evacuate Collection Set - 가비지 컬렉션 사전 준비
2. Merge Heap Roots - 여러 Remembered Set을 하나로 합쳐 중복 제거
3. Evacuate Collection Set - 객체를 복사하고, 참조를 업데이트
4. Post Evacuate Collection Set - 후처리 및 다음 애플리케이션 실행 준비

profile
안정적인 쳇바퀴를 돌리는 삶

0개의 댓글