개념잡기

가비지 컬렉션

gucoding 2025. 7. 16. 23:37

가비지 컬렉션

기본개념

자바 기준으로, JVM Heap 영역에 동적으로 할당된 메모리 중 더이상 사용하지 않는 영역에 대해서 주기적으로 정리하는 과정을 뜻합니다.

SomethingClass1 sc = new SomethingClass1();

free(sc)..?

쉽게 말해 이런식으로 객체를 생성해두었으면 원래는 개발자가 메모리를 해제하는 과정을 진행해야합니다.

그렇지않으면 메모리가 낭비되기 때문이죠. (메모리 누수)

c언어에서는 free() 함수로 직접 작성해줬던 기억이 나는데요, 자바에서는 이 과정을 가비지 컬렉터가 대신해줍니다.

기본과정

 

그렇다면 사용하지 않는 객체(쓰레기, Garbage)는 어떻게 판단해야할까요? 단순히 하나하나 검사하기에는 많은 시간이 소요되어 프로그램 성능에 치명적일 것입니다. 그래서 HotSpot VM(대표적인 JVM)은 다음과 같은 기법을 제시했습니다.

https://docs.oracle.com/en/java/javase/24/gctuning/garbage-collector-implementation.html#GUID-23844E39-7499-400C-A579-032B68E53073

  • 또한 가비지 컬렉션이 일어날 때는 GC 관련 스레드를 제외한 모든 스레드가 멈추게 된다.(Stop-The-World)

💡

약한 세대별 가설(Weak Generational Hypothesis)

  • 대부분의 객체는 생성된 지 얼마 안 되어 바로 쓰레기가 된다! 그리고 일단 오래 살아남은 객체는 앞으로도 계속 오래 살아남을 가능성이 높다!


이 가설은 다음 표에서 나타나듯 많은 프로그램에서의 수명을 관찰하다가 나온 결론입니다.

  • 가로축(x축): 객체의 수명 (얼마나 많은 바이트가 할당되는 동안 살아있었는지)
  • 세로축(y축): 해당 수명을 가진 객체들의 총 바이트 크기



이런 특징을 바탕으로 힙 영역(메모리 공간)을 젊은 세대(Young Generation)와 오래된 세대(Old Generation)로 나누고, 각 세대에 맞는 효율적인 방식으로 메모리를 청소합니다. 이렇게 하면 매번 모든 객체를 검사할 필요 없이, Young 영역을 집중적으로 청소하여 훨씬 빠르게 쓰레기를 회수할 수 있게 됩니다.

Young 영역

Young 영역에는 Eden 영역과 두개의 Survivor 영역으로 나뉩니다. 이 세 공간이 어떻게 협력해서 객체를 처리하는 지 알아봅시다.

  • 객체 생성: 대부분의 새로운 객체는 에덴 영역에 처음 만들어집니다.
  • 첫 번째 가비지 컬렉션 (Minor GC)
    • 에덴 영역이 꽉 차면, Minor GC라는 가비지 컬렉션이 시작됩니다.
    • 이때 에덴 영역과 현재 사용 중인 서바이버 영역(S0)에 있는 객체들을 검사합니다.
    • 쓰레기 객체: 더 이상 참조되지 않는 쓰레기 객체들은 그냥 사라집니다. (메모리가 회수되죠.)
    • 살아있는 객체: 아직 사용 중인 객체들은 비어있는 다른 서바이버 영역(S1)으로 복사됩니다.
    • 이 과정이 끝나면, 에덴 영역과 원래 사용 중이던 서바이버 영역(S0)은 깨끗하게 비워집니다.
  • 다음 가비지 컬렉션
    • 이제 에덴 영역이 다시 채워지고, 다음 마이너 GC가 발생합니다.
    • 이번에는 두 서바이버 영역의 역할이 바뀝니다. 이전 GC에서 채워졌던 서바이버 영역(S1)이 "원본"이 되고, 나머지 비어있던 서바이버 영역(S0)이 "목적지"가 됩니다.
    • 에덴 영역과 이전 원본 서바이버 영역(S1)의 살아있는 객체들은 다시 비어있는 서바이버 영역(S0)으로 복사됩니다.
    • 이 과정을 통해 객체들은 서바이버 영역 사이를 왔다 갔다(복사) 하면서 "나이(age)"를 먹습니다. 즉, GC 사이클을 몇 번이나 견뎠는지 카운트됩니다.
  • 객체의 노화 (Aging) 및 승진 (Promotion)
    • 이렇게 서바이버 영역 사이를 여러 번 왔다 갔다 하면서 살아남은 객체들, 즉 일정 횟수 이상 GC를 버텨낸 객체들은 "오래 살아남을 가능성이 높은 객체"로 간주됩니다.
    • 이런 객체들은 결국 올드 세대이동(승진)하게 됩니다.
    • 또는, 객체 하나의 크기가 너무 커서 서바이버 영역에 들어갈 공간이 부족한 경우에도 바로 올드 세대로 이동할 수 있습니다.

💡

Eden → (Survivor1 ↔ Survivor2) → Old

Old 영역

오랫동안 살아남은 객체들이 모이는 곳입니다. 영 세대보다 훨씬 크게 설정되며, 가비지 컬렉션도 영 세대만큼 자주 일어나지 않습니다. 올드 세대에서 발생하는 GC는 Major GC 또는 Full GC라고 부르는데, 이는 힙 전체를 검사하기 때문에 마이너 GC보다 훨씬 많은 시간과 자원을 소모할 수 있습니다.

G1

메모리 구조

앞서서 가비지 컬렉션 동작과정을 힙 구조 Young/Old 세대별 컬렉션 전략으로 설명했습니다.

대부분의 GC 알고리즘(가비지 컬렉터 종류)은 위 개념을 따르지만 각 GC 알고리즘은 이 기본 구조를 어떻게 구현하고 어떤 전략으로 메모리를 관리하느냐에 따라 힙의 내부 구조나 동작 방식에 큰 차이를 보입니다.

최근 자바버전의 공식문서를 살펴보면 G1 가비지 컬렉터를 사용하는 걸 알 수 있습니다.

Garbage-First Garbage Collector
자바 애플리케이션의 메모리를 관리하는 특별한 "쓰레기 청소부"입니다. 다른 청소부들보다 좀 더 똑똑하고, 특히 큰 메모리(힙)를 다룰 때 빛을 발합니다.


![image.png]

  • Eden (빨간색): 대부분의 새로운 객체는 여기에 처음 만들어집니다.
  • Survivor (빨간색, "S" 표시): 에덴에서 살아남은 객체들이 잠시 머무는 곳입니다.
  • Old (옅은 파란색): 젊은 세대에서 오랫동안 살아남아 '승진'한 객체들이 모이는 곳
  • H (옅은 파란색, "H" 표시): 특별히 매우 큰 객체

기존에는 메모리를 Young / Old 만 나누었지만 이 알고리즘에서는 각 메모리를 작은 구역으로 쪼개고 각 구역들은 필요에 따라 Eden, Survivor, Old 가 될 수 있습니다.

이런 메모리 구조덕분에 큰 객체가 들어와도 흩어져서 할당할 수 있고 이 블록은 그때그때 필요한 역할을 맡을 수 있어서, 메모리 할당과 청소에 훨씬 더 유연하고 효율적입니다.

→ 기존 GC처럼 힙 전체를 한 번에 스캔하고 처리하는 대신, G1은 이 개별 영역 단위로 GC 작업을 수행

→ Stop-The-World 시간 감소

동작과정

G1 GC는 메모리를 청소하는 데 크게 두 가지 단계를 번갈아 가며 수행합니다. 마치 '평상시 쓰레기통 비우는 단계'와 '대청소 단계'로 나누는 것과 비슷합니다.

1. Young-Only : 평상시 쓰레기통 비우기

  • 시작: 이 단계는 몇 번의 Normal young collection 으로 시작합니다.
    • Normal young collection : 새로 만들어진 객체들이 있는 Young 영역을 주로 청소하고, 여기서 오래 살아남은 객체들은 Old 영역에 쌓아둡니다.
    • 위에서 살펴본 Minor GC 과정이네요.
  • Concurrent Start : 대청소 준비
    • Old 영역이 특정 비율(시작 힙 점유율 임계값, Initiating Heap Occupancy threshold) 이상으로 채워지면, G1은 이제 '대청소'를 준비해야겠다고 생각합니다.
    • 이때 G1은 Normal young collection 대신 Concurrent Start young collection을 실행합니다.
      • Concurrent Start young collection : 이 컬렉션은 Young 영역 청소와 더불어, Old 영역에 있는 객체들 중에서 살아있는 객체가 무엇인지를 찾아내는 작업(마킹 프로세스)을 애플리케이션이 돌아가는 동안(동시적으로) 시작합니다.
      • 마킹 작업이 완전히 끝나기 전에도 일반 영 컬렉션은 계속 발생할 수 있습니다.
    • 특이 케이스: 만약 이 '동시 시작' 단계에서 "어? Old 영역에 별로 쓰레기가 없는데?"라고 G1이 판단하면, 마킹 작업을 중단하고 다시 영-온리 단계로 돌아가 평상시 청소를 계속합니다. (이러면 아래의 Remark/Cleanup은 발생하지 않습니다.)
  • 마킹 마무리 (Remark & Cleanup):
    • 동시 마킹 작업이 거의 끝나면, 두 번의 짧은 Stop-The-World 가 발생합니다.
      • Remark: 마킹 작업을 최종적으로 마무리하고, 사용되지 않는 클래스나 참조들을 정리하며, 완전히 비워진 구역들을 회수합니다. 이 단계에서 G1은 나중에 동시적으로 회수할 메모리 공간에 대한 정보도 계산합니다.
      • Cleanup: 이 단계에서 대청소 단계(Space-Reclamation phase)로 들어갈지 최종적으로 결정합니다. 만약 대청소가 필요하다고 판단되면, G1은 마지막으로 Young 영역을 청소하며 Young-Only 단계를 마무리합니다.

2. 공간-회수 단계 (Space-Reclamation phase): 본격적인 대청소

  • 시작: 이 단계에서는 Mixed collection 이 여러 번 발생합니다.
    • Mixed collection : 이름 그대로, Young 영역을 청소하는 것뿐만 아니라, Old 영역에서 쓰레기가 많은 곳들을 선택해서 함께 청소합니다.
      • 필요한 부분만 효율적으로 청소하여 애플리케이션의 중단 시간을 최소화
    • 이 과정에서 G1은 가장 효율적으로 메모리를 회수할 수 있는 Old 영역을 우선적으로 복사해서 이동시킵니다.

💡

이 과정의 핵심은 Young 영역에서 살아남은 객체들을 Old 영역으로 복사(Evacuation)

하는것은 세대이동에 의의가 있고 Old 영역에서의 복사는 메모리 압축에 의의가 있습니다.

앞에서 말했듯이 메모리 구조를 작은구역을 쪼개었기 때문의 기존에 비해서 메모리 단편화 문제에 자유로워 진 것은 사실이나, 아예 없는 것은 아니라고 합니다. 만약에 Old 영역을 압축하지 않고 있다가 메모리 단편화 현상이 발생한다면 그 때는 Full GC 가 실행되는데 이는 기존 GC와 마찬가지로 힙 전체를 압축하는 매우 긴 STW 를 유발합니다.

'개념잡기' 카테고리의 다른 글

웹 공격으로 생각해보는 JWT  (0) 2025.06.02
캐시  (1) 2025.05.28