Java G1 GC와 Humongous Objects and Humongous Allocations

Java Mark & Sweep, G1 GC, Humongous Objects and Humongous Allocations
이민석's avatar
Apr 13, 2025
Java G1 GC와 Humongous Objects and Humongous Allocations

A. 서론

DevOps가 바라본 SpringBoot (+ JVM) [A-1] 작성 이후,
Toss Slash 21, SRE 사례 소개하기 [A-2]를 보면서,
G1 GC Humongous Objects and Humongous Allocation 이슈를 알게 되었습니다.

💡

SpringBoot에서 대량의 매트릭을 수집하면서, G1GC Humongous Objects and Humongous Allocation 관련해서 발생하면서 큰 용량의 메모리 처리에서 Timeout이 발생함

G1 GC Humongous Objects and Humongous Allocation 이전에
G1 GC을 이해하고 싶었고 이를 위해서 CMS GC, Mark Sweep, Reference Counting까지 단계적으로 존재를 알게 되었습니다.

위 이슈를 이해하기 위해 역순으로 짚어나가면서 공부한 기록을 남기고자 합니다.

🚫

이 문서는 "추상화"에 집중한 문서이며, 다양한 참고자료를 교차검증하면서 G1 GC Humongous Objects and Humongous Allocation 이슈를 이해하는데 집중하고자 합니다.

A.0. 최소한의 메모리 상식

대다수 프로그래밍 언어는 필요한 정보를 메모리에 할당*하고 참조*하여 사용합니다.

할당* : 물리 메모리의 특정한 장소에 데이터를 넣는 행위
참조* : 물리 메모리의 특정한 장소를 가리키는 주소지를 바라보는 행위

메모리에 할당된 정보가 사용하지 않는 시점에는 이를 회수*하여 공간을 확보합니다.

회수* : 물리 메모리의 할당*을 해제하고 이 공간은 할당 가능한 공간이 됨

일부 저수준 언어(C/C++)는 개발자가 할당, 회수 과정을 직접적으로 제어해야 합니다.
하지만 대부분의 고수준 언어(Java/Node/Golang)은 GC*에 이를 위임하게 됩니다.

GC* : Garbage Collector가 메모리 할당, 참조, 회수 등의 관리 작업을 대신함

이 과정에서 개발자는 메모리 누수의 위험이 줄어들고 편의성이 증가되지만
GC 작업 및 작업 과정에서 다양한 오버헤드가 발생하여 어플리케이션 성능이 저하됩니다.

A.1. Java Root Space란 무엇인가?

언어마다 다르겠지만 Java에서는 stack, native method, method area 등이 루트 공간에 해당합니다.

JVM Runtime Data Area내부의 참조관계
[그림 1] JVM Runtime Data Area내부의 참조관계 [우아한테크톡 참조]

A.2. Reference Counting 방식 [A.2-1]

참조 횟수(reference count)가 0인 경우에 회수하는 방식입니다.
참조 횟수는 루트 공간*에서의 객체 참조객체 간 참조의 합으로 연산합니다.

Reference Counting
[그림 2] Reference Counting

하지만 순환 참조(circular reference)가 발생하면, 참조 횟수는 0이 될 수 없습니다.
붉은 영역은 사용하지 않으나 할당된 상태인 메모리 누수(memory leak) 부분입니다.

Reference Counting과 순환 참조 문제
[그림 3] Reference Counting과 순환 참조 문제

A.3. Mark & Sweep 방식 [A.2-1]

루트 공간에서의 접근이 불가능(unreachable)한 경우 회수하는 방식입니다.
대상을 찾는 Mark, 대상을 지우는 Sweep, 정돈하는 Compact가 있습니다.
(프로그래밍언어의 GC 구현체에 따라서 Compact는 생략되는 경우도 있습니다.)

  1. Mark : 루트 공간에서 접근 가능한 친구를 탐색

Mark Sweep 중 Mark 과정 (1/3)
[그림 4] Mark Sweep 중 Mark 과정 (1/3)

  1. Sweep : 마킹 되지 않은 친구들을 제거

Mark Sweep 중 Sweep 과정 (2/3)
[그림 5] Mark Sweep 중 Sweep 과정 (2/3)

  1. Compaction : 메모리가 흩어져있는 파티셔닝 현상을 정렬함으로써 완화

Mark Sweep 중 Compact 과정 (3/3)
[그림 6] Mark Sweep 중 Compaction 과정 (3/3)

Mark & Sweep은 메모리 관리 전략(방법) 중 하나입니다.
다양한 프로그래밍 언어*에서 Mark & Sweep을 응용해서 사용하고 있습니다.

💡

Java, Node.js, Golang GC는 모두 Mark & Sweep을 사용한다. Java는 JDK 9 전에는 CMS(Concurrent Mark Sweep)을 사용하다가 현재에는 G1(Garbage First Mark Sweep) 방식을 사용하고 있다. Node.js, Golang은 모두 CMS(Concurrent Mark Sweep)을 사용하고 있으나, 최신 버전에서는 달라질 수 있으므로 확인이 필요하다.

바로 이어서 CMS GC, G1 GC에 대해서 배우기 전에,
Mark & Sweep의 특징두가지 스레딩 방식을 알고 넘어가는 것이 좋습니다.

이러한 Mark & Sweep 방식은 2가지 특징이 존재하며,
이 중에서 대기(wait)가 일어나는 현상을 StW(Stop the World)라고 부르며,
StW가 일정 시간(duration)보다 길어지면 서비스 자체에 장애가 발생할 수 있습니다.

  1. 의도적으로 GC를 실행시켜야 한다.

  2. GC 실행과 어플리케이션 실행이 병행하며,
    GC 실행이 되는 동안 어플리케이션이 대기(wait)하는 현상이 발생한다.

Mark & Sweep 방식은 스레딩 구조에 따라서 2가지로 구분되며,
Java에서는 Parellel GC를 기본값으로 사용하고 있습니다.

  1. Serial GC(signle-thread) : 어플리케이션 대기 시간이 길다.

  2. Parellel GC(multi-thread) : 어플리케이션 대기 시간이 짧다.

💡

Seiral GC는 sigle-thread java + small heap 환경에서 최초 사용 되었으나multi-thread java + large heap 환경으로 인해 Parrel GC가 도입되고 기본 GC로 사용되고 있다.

A.4. CMS GC 방식 (작성 필요)

CMS*(Concurrent Mark Sweep) GC 방식
JDK 9 @deprecated JDK 14 @removed

  1. Initial Mark : GC Root에서 참조하는 객체 마킹 (StW)

  2. Concurrent Mark : 이전 단계에서 참조한 모든 객체를 마킹 (Non StW)

  3. ReMark : 이전 단계에서 참조된 객체를 다시 찾아, 추가 및 참조 해제된 객체를 확정 (StW)

  4. Concurrent Sweep : 도달할 수 없는 객체들을 삭제 (Non StW)

💡

CMS GC 작동 원리에 대해서 간단하게만 알아두는 정도로... 나머지는 전부 추상화

A.5. G1 GC 방식 (작성 필요)

G1 (Garbage Firsrt) GC 방식

JVM Heap 내부 구조
[그림 00] JVM Heap 내부 구조

G1 GC에서 이루어지는 가비지 컬랙팅은 크게 3가지로 구분됩니다.

  1. Minor GC

    1. Young Generation 내부에서 작동

    2. Eden 및 꽉찬 Survivor에 있는 객체를 텅빈 Survivor로 옮기고 나머지 청소

  2. Promotion

    1. Young Generation 내부에서 작동 시작

    2. Survivor 사이를 오가는 객체들의 age-bit가 일정 수준이 넘어가면 해당 객체를 Old Generation으로 이동

  3. Major GC(Full GC)

    1. Old generation의 공간이 가득 찼을때 Mark Sweep 방식으로 지워짐

A.6. G1 GC Humongous Objects & Humongous Allocation

G1 GC에는 Heap Region이 있고
객체가 Heap Region size의 1/2 이상의 메모리가 필요한 경우
Heap Region에 바로 할당할 수 없고 Old Generation 영역에 G1 GC Humongous Objects & Humongous Allocation

이렇게 Humongous Objects들이 대량으로 생성되면서,
Old Generation 공간이 부족해져서 Major GC(Full GC)가 발생하고
결과적으로 Timeout 문제까지 연결될 수 있습니다.

  1. 객체의 크기 줄이기

  1. Heap Region Size 늘리기

  2. Stream 방식 활용

A.7. G1 GC 분석하기

💡

G1 GC 분석을 경험하고 나서 그 경험을 살려서 이 글을 이어서 쓸 것 같습니다.

  1. 기본값 설정 확인하기

    java -XX:+PrintCommandLineFlags -version
    -XX:ConcGCThreads=2 -XX:G1ConcRefinementThreads=9 -XX:InitialHeapSize=268435456 -XX:MarkStackSize=4194304 -XX:MaxHeapSize=4294967296 -XX:MinHeapSize=6815736 -XX:+PrintCommandLineFlags -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:+UseCompressedOops -XX:+UseG1GC 
  2. Eden, S0, S1, Old Menegartion 횟수 및 시간 확인하기

    jstat -gcutil -t 8844 1000 10
  3. Eden, S0, S1, Old Menegartion 용량 확인하기

    jstat -gccapcity -t 8844 1000 10

참고 자료

  1. Blog (Crash GCeasy) - Java CMS GC Tuning

  2. Blog (Crash GCeasy) - Reading & Analyzing G1 GC Logs: A Step-by-Step Guide

  3. Blog (Crash GCeasy) -Simple & effective Java G1 GC tuning tips

  4. Blog (우당탕탕) - [Java] 가비지 컬렉션(Garbage Collection) 알고리즘 (가비지 컬렉션 - 2)

  1. Blog (F-Lab) - 효율적인 가비지 컬렉션: G1GC의 이해와 활용

  2. Blog (Leaphop) - G1GC Garbage Collector에 대해 알아보기 - 1

  3. Blog (Leaphop) - G1GC Garbage Collector에 대해 알아보기 - 2

  4. Blog (Leaphop) - G1GC Garbage Collector에 대해 알아보기 - 3

  5. Blog (뽕) - [JVM] GC 기본개념 - JVM메모리 구조 / Minor GC / Full GC

  6. Youtube (우아한테크) - [10분 테코톡] 조엘의 GC

Share article

Unchaptered