Garbage First Garbage Collector(G1GC)란?

soodo·2023년 12월 27일
0

Java

목록 보기
1/1
post-thumbnail

🧹G1GC - Garbage First Garbage Collector

  • Java는 CG를 이용해 개발자가 직접 메모리를 관리하지 않고 개발할 수 있도록구현된, 매니지드 언어다.
  • GC는 대부분의 객체가 곧(금방) 도달할 수 없게 된다는 가설에 의해 동작한다.
  • G1은 stop-the-world에 의한 일시 중지 시간을 줄이는 것을 목표로한다.
  • G1GC는 Heap영역을 체스판처럼 여러 영역으로 나누어 관리한다.
    • 따라서 G1은 영역의 참조를 관리할 목적으로 remember set을 만들어 사용한다.

A. G1GC의 기본 개념

  • 다른 GC와 마찬가지로 young, old generation으로 나뉜다.
    • 공간 재확보는 young세대에 집중되고, old세대에는 간헐적으로 수행된다.
  • 처리량을 향상시키기 위해 일부 작업은 stop-the-world로 이루어지지만, 전역 표시와 같은 전체 힙 작업과 같이 응용 프로그램이 중지되면 더 많은 시간이 걸리는 다른 작업은 응용프로그램과 동시에 병렬적으로 수행된다.
    • whole-heap operations like global marking
  • stop-the-world를 짧게 유지하기 위해 G1은 공간 재확보를 단계적, 점진적으로 병렬로 수행한다.
  • G1은 관련 비용 모델을 구축하기 위해 이전 애플리케이션 동작 및 가비지 수집 일시 중지에 대한 정보를 추적하여 예측 가능성을 달성하고, 이 정보를 가지고 일시 중지에서 수행된 작업의 크기를 재조정한다.
    • ex) G1은 가장 효율적인 영역(대부분 쓰레기로 채워진 영역)의 공간을 먼저 회수한다.
  • G1은 주로 대피를 사용해 공간을 회수한다.
    • 수집하기위해 선택된 메모리 영역 내에서 발견된 라이브 객체는 새로운 메모리 영역으로 압축, 복사된다.
    • 이 복사 과정이 조각 모음의 역할도 한다.
    • 비우기 과정이 완료된 영역은 애플리케이션 할당에 재사용됩니다.
  • G1GC는 실시간 수집기가 아니다. 더 오랜시간 동안 높은 확률로 설정된 일시 중지 시간 목표를 충족하려 하지만, 주어진 일시 중지 시간에 대해 항상 절대적인 확실성을 갖지는 못한다.

B. G1GC의 내부 구조

힙 레이아웃

  • G1은 힙을 동일한 크기위 힙 영역 집합으로 분할한다. 다른 GC와 달리 각 세대의 영역이 메모리에서 비연속적 패턴으로 배치된다는 차이를 가진다.
    • 이때, 하나의 영역이 메모리 할당과 메모리 회수의 단위로 사용된다.
  • 각 영역은 특정 세대(young or old)에게 할당되거나 비어있다.
  • 메모리 요청이 들어오면 관리자는 여유 영역을 할당한다.
  • old generation의 경우 여러 영역에 걸쳐있는 객체의 경우 거대할 수 있다.
  • 애플리케이션은 항상 젊은 에덴 영역에 할당되지만, 이전 세대에 속하는 것으로 집적 할당된는 경우 예외가 있다.
  • G1의 일시 중지는 Young Generation 전체 공간을 회수할 수 있으며, 수집 일시 중지 시 Old Generation의 추가 집합을 회수할 수 있다.
  • 일시정지 중 G1은 컬렉션 세트에서 객체를 힙의 하나 이상의 다른 영역으로 복사한다.
    • young 영역의 객체는 survival 또는 old 영역으로 복사
    • old 영역의 객체는 또다른 old영역으로 복사

초기 힙 점유 결정

  • IHOP(Initiating Heap Occupancy Percent)는 Young-Only 단계의 Normal Young Collection이 수행되다가 Concurrent Start Young Collection이 트리거되는 임계값이며, Old 세대 크기의 백분률로 정의된다.
  • G1은 기본적으로 마킹 단계 동안 마킹에 소요되는 시간, Old 세대에서 일반적으로 할당되는 메모리의 양을 관찰해 IHOP를 결정하고, 이것을 적응형 IHOP라고 한다.
  • -XX:InitiatingHeapOccupancyPercent:
    • 이 옵션을 사용하면, 통계 자료가 충분하지 않은 초기 단계에서 해당 값을 초기값으로 사용한다.
  • XX:-G1UseAdaptiveIHOP:
    • 이 옵션으로 적응형 IHOP를 끌 수 있고, 지정된 값을 계속 사용한다.

마킹

  • G1의 마킹은 SATB(Snapshot-At-The-Beginning)알고리즘을 사용한다.
  • SATB는 일시 정지가 일어난 시점 직후 힙의 가상 스냅샷을 생성하고, 해당 시점에 살아있는 객체는 이후 마킹과정에서 죽더라도 살아있는 객체로 간주한다. - 보수적
  • 다른 수집기에 비해 일부 메모리 낭비가 있을 수 있지만, Remark단계의 응답 시간이 다른 GC에 비해 빠른 특징을 갖는다.

Evacuation Failure

  • 애플리케이션이 너무 많은 메모리를 활성 상태로 유지해 대피할 공간이 없는 경우 대피 실패가 발생한다.
  • 이때는 이미 이동시킨 객체는 새 위치 그대로 유지하고, 아직 이동시키지 않은 객체는 대피시키지 않습니다. 또한 이동시키지 않은 객체는 참조를 끊지 않도록 조정해 무사히 GC 작업을 마치는 방향으로 진행된다.
  • 대피 실패로 인해 약간의 오버헤드가 발생하지만 일반적인 Young Collection만큼 빠르며, 대피 실패이후 GC 작업이 완료되면 정상적으로 애플리케이션을 재개한다.
  • G1은 GC가 끝날 무렵 대피 실패의 뒷수습이 끝났다고 가정한다.
    • 이 가정이 유지되지 않으면, 즉 애플리 케이션 실행에 공간이 부족하면 Full GC가 예약된다..

Humongous Object

  • 거대한 객체는 한 영역의 절반 이상의 크기를 갖는 객체를 말한다.
    • 영역의 크기가 기준이 되므로 -XX:G1HeapRegionSize옵션의 영향을 받받는다.
  • 거대한 객체는 크기 때문에 특별한 방식으로 취급된다.
    • 모든 거대한 객체는 old 영역에서 연속 영역으로 할당
    • 마지막 꼬리 영역에 남는 공간이 생겨도 잉여 공간은 활용하지 않는다.
    • 거대한 객체를 할당하면 G1은 IHOP를 확인하고, 점유율이 임계값을 초과한 경우 즉시 Mark Young Collection을 강제할 수 있다.
    • 거대한 객체는 Full GC를 포함해 절대 움직이지 않는다.
      • 결과로, 공간의 조각화로 인해 공간이 충분한데도 메모리 부족 상태가 발생할 수 있다.
      • 결과로, Full GC가 느려질 수 있다.

C. 🔁 수집 주기

크게보면, G1 컬렉터는 두 단계 사이를 번갈아 가며 움직인다.

  • young 전용 단계에서는 현재 사용 가능한 메모리를 old 세대의 객체로 점차 채우는 과정이 수행된다.
  • 공간 회수 단계에서는 G1이 Young 세대를 처리하는 것 외에도 Old 영역의 공간 또한 점진적으로 회수한다.

1. Young-only phase

1.1 Normal Young Collection

  • Eden 영역이 가득차면 일반적인 Young GC가 발생된다.
  • 특정 영역에 대해서 발생하고, 모든 Young Generation 영역이 타겟이다.
  • 각 영역 별 생존 객체들은 Survival 영역으로 이동하고, age count가 임계값 이상이 되면 old 영역으로 이동한다.
  • Old 세대의 점유가 Initialing Heap Occupancy 임계값에 도달할 때 Concurrent Start Young Collection으로 전환된다.
  • 이때 G1은 일반적인 young 컬렉션 대신 Concurrent Start Young 컬렉션을 예약한다.

1.2 Concurrent Start Young Collection

  • 이 컬렉션에서는 Young GC + Mark 프로세스를 추가로 실행한다.
  • 현재 old영역의 live 객체에 대해 마킹을 진행하고, 해당 객체가 이후 공간 재확보 단계에서 유지되도록 한다.
  1. initial Mark - STW 발생
    1. Old to Young 참조와 GC Root Set을 이용해 Root 객체들을 찾고 마킹한다.
    2. 이 단계에서는 STW를 통해 모든 쓰레드를 일시적으로 중단함으로써 스냅샷을 찍고 살아있는 객체와 그렇지 않은 객체를 구분한다.
    3. STW가 발생하지 않으면, 애플리케이션 쓰레드가 객체의 참조를 계속해서 바꿀 수 있으므로 일관된 객체의 상태를 보장할 수 없다.
  2. The Concurrent Marking Phase
    1. 전체 Heap에서 라이브 객체를 찾고 마킹한다.
    2. 이 단계는 애플리케이션 쓰레드와 동시에 실행되며, STW를 발생시키지 않는다.
    3. 이 단계에서는 STW 시간을 줄이기 위해서 존재합니다. STW를 발생시키지 않고, 대부분의 마킹을 끝내고 Remark 단계에서 마무리한다.
  3. Remark - STW 발생
    1. 이 단계에서는 이전 Concurrent Mark phase에서 동시 실행 중 변경된 참조에 대해서 마킹을 마무리하는 단계다.
    2. 해당 단계에서 STW는 변경을 추적하기 위해 필요합니다. 이전 동시 마킹 단계에서의 부정확한 마킹을 마무리하고, 참조가 변경된 객체들에 대해서 새롭게 마킹을 할 수 있다.
    3. 이 단계와 CleanUp 단계 사이에 G1은 선택된 Old 영역에서 공간을 동시에 확보할 수 있도록 정보를 계산한다.
  4. CleanUp - STW 발생
    1. 마킹되지 않은 객체를 삭제하고, 해당 영역을 회수한다.
    2. 이 단계에서는 거대한 객체에 대해서도 공간 회수를 진행한다.
    3. 이 단계에서는 실제로 공간 재확보 단계가 이어져야 하는지 판단한다.
      • 공간 재확보 단계에서 Old 영역의 객체들이 Young 영역으로 이동하기 때문에 Young 영역의 사용량과 여유 공간을 고려해 결정
      • 충분치 않다면 initial Mark 단계부터 다시 시작
    4. 공간 재확보 단계가 이어질 경우 Mixed-GC를 예약
    5. 해당 단계에서는 부분적으로 STW가 발생한다.
      • remark 단계 이후 변경된 객체들을 마킹하기 위해
      • 참조들을 지우고, 공간을 회수하기 위해 → 발생하지 않으면 참조들이 변경되 다시 mark 작업을 진행해야 할 수 있다.

2. Space-reclamation phase

  • 공간 재할당 단계에서는 Mixed GC를 수행한다.
  • Mixed GC를 수행하는 단계이기 때문에 Collection Set을 사용해, GC 대상 영역을 저장하는데, young olny 단계에서 계산한 결과를 가지고 Collection Set에 포함 될 Old 영역을 선출한다.
  • 공간 재할당 단계에서는 G1이 Old Generation 영역을 더 비우는 것이 작업 효율이 떨어진다고 판단할 때 종료되고, 다시 Young-Only 페이즈로 전환된다.
    • 앞서 Young Only 페이즈에서와 동일한 일이 일어난다.

Mixed GC

  • Mixed GC는 Full GC와 달리 모든 Young Generation과 Old Generation의 일부를 대상으로 한다.
    • 일시 정지 시간 목표를 기반으로 수집 할 영역 수를 선택
  • 이전에 계산한 정보를 사용해 Mixed GC의 대상이 될 지역을 Collection Set에 저장한다.
  • Collection Set에 포함 된 지역의 생존 객체들은 새로운 지역으로 대피하고, 해당 지역을 회수한다.

3. Full GC

  • G1은 애플리케이션의 메모리가 부족하면 다른 GC와 마찬가지로 Full GC를 수행합니다.

D. GC를 위한 자료구조

Card-Table

  • Card Table은 Old Generation to Young Generation 참조가 있는지 확인하기 위한 자료구조다.
  • Card Table의 각 바이트를 카드라고 부르며, 카드의 수는 힙의 주소 범위와 같다.
  • 해당 바이트가 Dirty 하다는 것은, Old to Young 참조가 있다는 것을 의미한다.
    • ex) 만약 A 객체가 Old 영역의 B객체의 참조를 받고있을 때, Young GC에서는 해당 객체를 삭제해서는 안됩니다. 이때 CT가 사용된다.
  • Young GC를 실행할 때, Old Generation 모두를 뒤지는 것은 비효율적이기 때문에 Card Table이라는 구조를 만들어 사용한다.

Remember-Set

  • Remember Set은 G1GC에서 각 region의 객체의 참조를 추적하기 위해 사용하는 자료구조다.
  • 각각의 리전마다 R-Set을 유지하며, 해당 리전에 존재하는 객체가 다른 지역의 객체를 참조하고 있다면 R-Set에 기록한다.
  • R-Set은 비트맵 형태 혹은 또다른 형태로 구성되며, 각 비트는 특정 객체가 다른 리전의 객체를 참조하는지 여부를 나타낸다.
    • Sparse, Fine, Coarse 3가지로 나뉜다.
  • Per-Region-Table - 리전 당 하나
    • G1 Heap 내의 모든 Region을 대상으로 작동하는 데이터 구조다.
    • 각 Region에 대한 엔트리가 있으며, 이 엔트리는 해당 Region이 수집 대상인지 여부를 나타내는 플래그와 해당 Region에 대한 작업에 대한 정보를 저장한다.

Collection-Set

  • CSet은 Evacuation Pause(대피 일시정지)를 소화하기 위한 자료구조
  • CS의 생존 객체들은 GC 사이클 동안 대피/복사된다.
  • Young Collection Set
    • 모든 Young 지역을 포함한다.
  • Mixed Collection Set
    • 모든 젊은 영역과 몇개의 Old 영역을 포함한다.
    • 세팅에 따라 최소, 최대 포함할 수 있는 Old 영역의 수가 정해진다.

기타 자료구조들

Free List

  • JVM내부 가용한 Heap의 크기를 추적하기 위해 사용하는 자료구조
  • Heap 크기와 같은 여러 정보가 저장된다.

Age Table

  • 객체의 Age를 추적해 Old 영역으로 이동시키기 위한 자료구조

🙄 why G1GC?

  • 공부하면서, G1GC의 장점은 알겠는데 그게 왜 좋을까를 생각해봤다.
  • GC 시간을 줄여준다는 장점도 있지만, 결국 빈번하게 일어나기 때문에 이것만으로 사용하지는 않을 것 같다. 
  • 개인적인 생각으로는 G1GC는 최대 일지 정지 시간을 충족하며 동작하기 때문에 우리의 애플리케이션이 예측 가능한 범위 내에서 동작시키는데 도움을 줄 수 있을 것 같다.
    • 아마 이 부분 때문에 G1GC를 사용하지 않을까??

오류가 있다면, 댓글로 남겨주시면 감사하겠습니다.!😅


참고

https://docs.oracle.com/en/java/javase/11/gctuning/garbage-first-g1-garbage-collector1.html#GUID-ED3AB6D3-FD9B-4447-9EDF-983ED2F7A573
https://plumbr.io/handbook/garbage-collection-algorithms-implementations#g1
https://stackoverflow.com/questions/40182392/does-java-garbage-collect-always-has-to-stop-the-world/40200257#40200257

https://www.infoq.com/articles/tuning-tips-G1-GC/
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc.html
http://psy-lob-saw.blogspot.com/2014/10/the-jvm-write-barrier-card-marking.html

https://johngrib.github.io/wiki/java/gc/g1gc/#humongous-object

0개의 댓글