Garbage Collection

Gunjoo Ahn·2022년 8월 8일
0
post-thumbnail

📌Garbage Collection이란 무엇일까?

C++과 같이 개발자가 직접 메모리 관리를 하지 않고, 언어레벨에서 사용하지 않는 메모리는 “알아서” 관리해주는 JVM 구성 요소이다.

메모리 관리를 위한 기본 컨셉은 Weak generational hypothesis 이다.

대부분의 객체는 금방 접근 불가능한 상태(unreachable)가 된다.
오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.

그래서 JVM에서 크게 2개의 물리적 공간으로 나누었다. Young 영역Old 영역이다.

Young 영역(Yong Generation 영역)

새롭게 생성한 객체의 대부분이 여기에 위치한다. 대부분의 객체가 금방 접근 불가능 상태가 되기 때문에 매우 많은 객체가 Young 영역에 생성되었다가 사라진다. 이 영역에서 객체가 사라질때 Minor GC가 발생한다고 말한다.

Old 영역(Old Generation 영역)

접근 불가능 상태로 되지 않아 Young 영역에서 살아남은 객체가 여기로 복사된다. 대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young 영역보다 GC는 적게 발생한다. 이 영역에서 객체가 사라질 때 Major GC(혹은 Full GC)가 발생한다고 말한다.

용어정리

  • MinorGC : Young Generation 에서 발생하는 GC
  • MajorGC : Old Generation (Tenured Space) 에서 발생하는 GC
  • FullGC : Heap 전체를 GC 하는 작업 (Young/Old 공간 모두)

Garbage Collection 프로세스 (Young to Old)

Eden 영역이 꽉 차면 Minor GC가 발생하게 되는데, 사용되지 않는 메모리는 해제되고 Eden 영역에 존재하는 객체는 (사용중인) Survivor 영역으로 옮겨지게 된다.

Survivor 영역은 총 2개이지만 반드시 1개의 영역에만 데이터가 존재해야 한다.

  1. 새로운 오브젝트는 Eden 영역에 할당된다. 두 개의 Survivor Space 는 비워진 상태로 시작한다.

  2. Eden 영역이 가득차면, MinorGC 가 발생한다.

  3. MinorGC 가 발생하면, Reachable 오브젝트들은 Survivor1 으로 옮겨진다. Unreachable 오브젝트들은 Eden 영역이 클리어 될때 함께 메모리에서 사라진다.

  4. 다음 MinorGC 가 발생할때, Eden 영역에는 3번과 같은 과정이 발생한다. Unreachable 오브젝트들은 지워지고, Reachable 오브젝트들은 Survivor Space 로 이동한다. 기존에 Survivor1 에 있었던 Reachable 오브젝트들은 Survivor2 로 옮겨지는데, 이때, age 값이 증가되어 옮겨진다. 살아남은 모든 오브젝트들이 Survivor2 으로 모두 옮겨지면, Survivor1 과 Eden 은 클리어 된다. Survivor Space 에서 Survivor Space 로의 이동은 이동할때마다 age 값이 증가한다. 1개의 Survivor 영역은 반드시 빈 상태가 된다.

  5. 다음 MinorGC 가 발생하면, 4번 과정이 반복되는데, Survivor2 가 가득차 있었으므로 Survivor2 에서 살아남은 오브젝트들은 Survivor1 으로 옮겨지면서 Eden 과 Survivor2 는 클리어 된다. 이때에도, age 값이 증가되어 옮겨진다.

  6. Young Generation 에서 계속해서 살아남으며 age 값이 증가하는 오브젝트들은 age 값이 특정값 이상이 되면 Old Generation 으로 옮겨지는데 이 단계를 Promotion 이라고 한다.

  7. MinorGC 가 계속해서 반복되면, Promotion 작업도 꾸준히 발생하게 된다.

  8. Promotion 작업이 계속해서 반복되면서 Old Generation 이 가득차게 되면 MajorGC 가 발생하게 된다.

📌GC 알고리즘

초기 알고리즘으로 대표적으로 Reference Counting Algorithm과 Mark-and-Sweep Algorithm이 있다. 추가로 몇가지 알고리즘을 더 다루어 보도록 하자.

Tri-color Marking은 나중에 다루어 보도록 하자.

  • Reference Counting Algorithm
  • Mark-and-Sweep Alogrithm
  • Mark-and-Compact Algorithm
  • Copying Algorithm
  • Generational Algorithm

STW(Stop The World)

GC 사이클이 발생하여 Garbage 수집하는 동안 모든 어플리게이션 스레드가 중단된다. GC 스레드 외에는 모두 작업을 멈춘다. 모든 GC 알고리즘은 STW를 한다. 대개의 경우 Garbage Collector 튜닝은 STW 시간을 줄이는 것이다.

Reference Counting Algorithm

Garbage란 자신을 참조하는 곳이 아무데도 없으면 Garbage이다. 이것을 찾기위해 자신이 자신의 참조값을 들고 있어, 만약 참조값이 0이면 자신을 참조하는 곳이 아무데도 없다는 의미이다. 문제는 순환참조를 잡아내지 못한다는 것이다.

Mark-and-Sweep Algorithm

Reference Counting Algorithm의 단점을 극복하기 위해 나온 Algorithm이다. Root Set에서부터 Reference의 관계를 추적하여 Garbage를 찾는 것이다. 그래서 Tracing Algorithm이라고도 불린다.

GC Root

GC Root는 메모리의 Anchor Point로 메모리 풀 외부에서 내부를 가리키는 포인터이다.

  • 실행중인 쓰레드 (Active Thread)
  • 정적 변수 (Static Variable)
  • 로컬 변수 (Local Variable)
  • JNI 레퍼런스 (JNI Reference)

Root Set에서 도달할 수 있는 객체는 Mark하고 그렇지 못한 객체는 Sweep과정으로 free하는 것이다.

Mark and sweep 만하면 메모리가 듬성듬성 사용하는 형태가 된다. 이럴경우 메모리가 있음에도 메모리 할당이 불가능한 상황이 생길 수 있다. 이것을 조각 모음할 필요가 있다.

Mark-and-Compact Algorithm

Mark and Sweep and Compact Algorithm이다. 살아남은 객체들이 연속된 메모리 공간으로 몰아 넣는 것이다. 메모리 공간이 변하기 때문에 Referece 업데이트 과정이 생겨 Overhead가 있다.

Copying Algorithm

Survivor Space에서 쓰는 알고리즘으로, Mark-and-Copy Algorithm이라고도한다. Mark-and-Compact와 다른 점은 조각 모음한 객체들이 들어가는 메모리 공간이다. Active메모리 공간에서 In-Active메모리 공간으로 이동하고, 이동한 곳을 Active로 남은 곳을 In-Active로 만들고 전부 free하는 것이다. 메모리 공간이 더 필요하다는 단점이 있다.

Generational Algorithm

현재 GC가 채택하고 있는 알고리즘이다. Weak generational hypothesis를 기반으로 하는 위에서 설명한 GC 프로세스가 바로 Generational Algorithm이다.

  1. 객체마다 Generational Count(객체가 지금까지 무사 통과한 가비지 수집 횟수)를 센다.
  2. 큰 객체를 제외한 나머지 객체는 Eden 공간에 생성한다.
  3. GC가 한 번 발생한 후 살아남은 객체는 Survivor 영역 중 하나로 이동된다.
  4. 충분히 오래 살아남은 객체들은 별도의 메모리 영역 Old(or Tenured)에 보관한다.

📌GC Implementations

  • Serial Garbage Collector
  • Parallel Garbage Collector
  • CMS Garbage Collector
  • G1 Garbage Collector
  • Z Garbage Collector

Serial Garbage Collector

싱글 스레드로 동작하는 GC이며, Mark-and-Compact Algorithm으로 동작한다.

Parallel Garbage Collector

Serial Garbage Collector에서 GC 스레드가 여러개가 된 것이다.

Minor GC를 멀티 스레드로 수행하면서 Promotion 작업이 여러 스레드에서 동시에 발생하면 Corruption이 발생할 수 있다.

PLAB (Parallel-Local Allocation Buffers)

  • TLAB과 동일한 개념이지만 PLAB은 Old 영역에서 발생하는 Lock을 막기 위해 나온 해결책이다.
  • Old 영역에서 객체의 promotion에 의한 메모리 할당이 있을 때 마찬가지로 bump-the-pointer 방식을 사용하는데, 이는 멀티 스레드 환경에서 Lock이 발생할 수 있기 때문에 TLAB과 마찬가지로 Old 영역을 작은 Buffers로 나누고 하나의 Thread는 자신이 가진 Buffer 영역에만 객체를 할당할 수 있다.
  • 하지만 이 방법을 사용하면 많은 Thread가 할당 받은 Buffer가 있어도 사용 되지 않거나, Buffer에 자투리 영역이 생기면서 메모리 단편화가 심해질 수 있다. 이 문제는 thread 개수를 감소 시키거나 Old 영역의 크기를 증가 시키는 것으로 해결할 수 있다.

Parallel Old Garbage Collector

이전 GC 방식은 Mark-Sweep-Compaction에서 단일 스레드가 Old 영역 전체를 훑는 방식이었다면, Parallel Old GC에서는 멀티 스레드가 Old 영역을 논리적으로 균일하게 나눈 Region(2KB 정도의 chunk) 단위로 GC를 수행한다.

Mark-Summary-Compaction 방식

CMS Garbage Collector

Concurrent Mark & Sweep의 약자로, Low latency Collection에 해당한다.

Java 14에 Drop 됐다

애플리케이션이 구동중일 때 프로세서의 자원을 공유하여 이용가능해야 한다. CMS GC가 수행될 때에는 자원이 GC를 위해서도 사용되므로 응답이 느려질 순 있지만 응답이 멈추지는 않게 된다.

하지만 이러한 CMS GC는 다른 GC 방식보다 메모리와 CPU를 더 많이 필요로 하며, Compaction 단계를 수행하지 않는다는 단점이 있다. 이 때문에 시스템이 장기적으로 운영되다가 조각난 메모리들이 많아 Compaction 단계가 수행되면 오히려 Stop The World 시간이 길어지는 문제가 발생할 수 있다.

G1 Garbage Collector

문제가 많은 CMS를 대체하기 위해 만들어진 방법으로, 하드웨어의 발전으로 큰 메모리에서 좋은 성능을 내는 것에 초점을 두어 CMS에 비해 Pause 시간을 개선했다.

물리적 Generation 구분을 없애고 전체 Heap을 1~32MB 단위의 Region들로 재편했다. (약 2048개의 Regions들로 나눌 수 있다.)

각 Region은 상태에 따라 역할이 동적으로 부여된다.

  • Eden - 새로 생긴 객체들이 할당되는 영역
  • Survivor - 살아있는 객체들이 할당되는 영역
  • Old - 오래 살아있는 객체들이 할당되는 영역
  • Humongous
    • 기존에 없던 영역
    • Region 크기의 50%가 넘는 큰 객체를 저장하기 위한 영역
    • GC 동작이 최적으로 동작하지 않는다.
  • Available / Unused
    • 기존에 없던 영역
    • 아직 사용되지 않는 영역

G1 GC의 핵심은 Heap을 동일한 크기의 Region으로 나누고, 가비지가 많은 Region에 대해 우선적으로 GC를 수행하는 것이다.

그리고 G1 GC도 다른 가비지 컬렉션과 마찬가지로 2가지 GC(Minor GC, Major GC)로 나누어 수행된다.

Young GC (STW가 발생)

  • 한 지역에 객체를 할당하다가 해당 지역이 꽉 차면 다른 지역에 객체를 할당하고, Minor GC가 실행
  • 가비지가 가장 많은(Garbage First) 지역을 찾아서 Mark and Sweep를 수행
  • STW 시간을 줄이기 위해 멀티 스레드로 작업한다.
  • Eden/Survivor 영역에서 수행된다. GC 후 살아있는 객체를 다른 Survivor Region으로 이동시키고, 비워진 Region을 Available Region으로 변경한다.

Old GC

  • 시스템이 계속 운영되다가 객체가 너무 많아 빠르게 메모리를 회수 할 수 없을 때 Major GC(Full GC)가 실행
  • Initial mark (STW 발생)
    • Old Region 객체가 참조하는 Survivor Region을 찾는다.
  • Root region scan
    • Initial Mark 단계에서 마킹된 Survivor Region에서 Old Region에 대해 참조하고 있는 객체를 마킹한다. 멀티 스레드로 동작하며 다음 Minor GC가 발생하기 전에 동작을 완료한다.
  • Concurrent mark
    • 전체 Heap 영역에 대한 스캔으로, 살아있는 객체가 존재하는 Region만 식별한다. 살아있는 객체가 없는 Region은 앞으로의 작업에서 제외된다.
    • STW가 발생하지 않으므로 애플리케이션 스레드와 동시에 동작하고, Minor GC와 같이 진행되므로 종종 Minor GC에 의해 중단될 수 있다.
  • Remark (STW 발생)
    • Concurrent Mark에서 Garbage만 있던 영역을 바로 회수하며, STW가 발생한다.
    • 최종적으로 살아남은 객체를 식별한다.
  • Cleanup
    • live object의 비율이 낮은 영역 순으로 순차적으로 수거해 나간다.
    • 살아남은 객체가 가장 적은 Region의 GC 대상을 제거한다. 이 작업에서 STW가 발생한다.
    • STW를 끝내고 GC로 제거되어 빈 영역은 Available Region으로 변경한다.
  • Copy
    • 살아남은 객체들을 Available Region에 복사하며 Compaction을 수행한다.

Z Garbage Collector

ZGC는 아래의 목표를 충족하기 위해 설계된 확장 가능하고 낮은 지연율을 가진 GC이다.

  • 정지 시간이 최대 10ms를 초과하지 않음
  • Heap의 크기가 증가하더라도 정지 시간이 증가하지 않음
  • 8MB ~ 16TB에 이르는 다양한 범위의 Heap 처리 가능

JVM으로 구동되는 애플리케이션의 경우, GC가 동작할 때 pause로 인해 성능에 큰 영향을 미쳐왔다. ZGC는 Load barrier와 Colored Pointer를 함께 사용해 puase 시간을 줄여 성능이 향상되었다.

Colored Pointer

객체를 가리키는 변수의 포인터에서 64bit를 활용해서 marking

  • Finalizable: finalizer를 통해서만 참조되는 Object의 Garbage
  • Remapped: 재배치 여부를 판단하는 Mark
  • Marked 1/0: Live Object

Load Barrier

어플리케이션 쓰레드는 힙 메모리에 있는 객체를 참조할때 Load barrier를 만나게 되는데, 이것은 JIT에 의해 주입된 코드다. 이 코드는 주소의 colored point(즉, GC메타데이터 비트)가 bad color인지 체크하고, 만약 bad color라면 객체를 상황에 따라서 mark/relocate/remapping 한다.

ZGC Flow

  1. Mark Start STW : ZGC의 Root에서 가리키는 객체 Mark 표시
  2. Concurrent Mark/Remap: 객체의 참조를 탐색하면서 모든 객체에 Mark 표시
  3. Mark End STW : 새롭게 들어온 객체들에 대해 Mark 표시
  4. Concurrent Prepare for Relocate: 재배치하려는 영역을 찾아 Relocation Set에 배치
  5. Relocate Start STW : 모든 Root 참조의 재배치를 진행하고 업데이트
  6. Concurrent Relocate: 이후 Load Barriers 를 사용하여 모든 객체를 재배치 및 참조 수정


알고리즘 별 메모리 그림 링크
Mark and Sweep 그림 링크
[JVM] Garbage Collection Algorithms
JVM Garbage Collection(GC)
[JVM] Garbage Collection
NAVER D2 Java Garbage Collection
오랜만에 Garbage Collection 정리
NAVER D2 Java Reference와 GC
자바 메모리 관리 - 가비지 컬렉션
[Java] Garbage Collection 동작 방식
[Java] 다양한 종류의 Garbage Collection(가비지 컬렉션) 알고리즘 (2/2)
[Java] G1 GC의 동작 과정
ZGC
ZGC에 대해서
JVM과 Garbage Collection - G1GC vs ZGC

알아두면 좋을 상식

  • Hotspot VM의 GC는 Arena라는 메모리 영역에서 작동한다.
  • Hotspot VM은 시작 시 메모리를 유저 공간에 할당/관리한다.따라서 힙 메모리를 관리할 때 시스템 콜을 하지 않으므로 커널 공간으로 컨텍스트 스위칭을 하지 않아서 성능 향상에 도움이 된다.
  • Hotspot VM은 할당된 메모리 가장 마지막의 다음 영역을 가리켜 연속된 빈 공간에 효율적으로 빠르게 할당하는 bump-the-pointer라는 기술을 사용했다.
  • Hotspot VM은 멀티 스레드 환경에서 객체를 할당할 때 스레드 간의 경합 등등의 문제를 줄이고자 TLAB(Thread Local Allocation Buffer)를 사용했다.Eden Space를 여러 버퍼로 나누어 각 어플리케이션 스레드에게 할당함으로써 자기 자신이 사용해야 할 버퍼를 바로 찾게되고, 리소스를 공유해서 생기는 문제를 없애버렸다.만약 본인에게 할당된 TLAB가 부족할 경우에는 크기를 동적으로 조정한다.

bump-the-pointer라는 기술을 써서 중간에 빈 공간이 있더라도 해당 공간에 할당하지 않는다.

그럼 Survivor Space의 단편화를 없애기 위하여 + minor GC까지 해서 mark and copy 알고리즘을 사용하는 것.

profile
Backend Developer

0개의 댓글