[Java] GC (Garbage Collection, Garbage Collector) 란?
Java

[Java] GC (Garbage Collection, Garbage Collector) 란?

728x90

현실 Garbage Collection

Garbage Collection 이란?

Garbage Collection, 직역하면 "쓰레기 수거"입니다.

말그대로 쓰레기를 수거하는데, 어디서 수거하느냐? 바로 메모리(RAM)에서 합니다. 우리가 컴퓨터에서 사용할 수 있는 메모리는 한정되어있습니다. 따라서 더이상 안쓰는 정보(쓰레기)를 메모리에서 제거해서 메모리 공간을 확보해주는 프로세스가 필요하죠. 이 작업을 Garbage Collection 이라고 합니다. 더 정확하게 말하면 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역을 해제하는 것이죠.

 

Java의 큰 장점이라고 한다면 이 Garbage Collection 이 자동으로 진행된다는 것입니다. 어디서 어떻게 진행되는지 알아보기 전에 먼저 Java 코드가 동작하는 방식을 복습해볼까요?

 

[Java] JVM, JRE, JDK?, [Java] JVM 동작 및 실행 과정 에서 다뤘듯, 자바 코드는 컴파일러를 통해 자바 바이트코드로 컴파일이 되고 이 바이트코드는 JVM 에서 기계어로 번역이되며 실행됩니다. 따라서 자바 프로그램은 컴퓨터의 모든 메모리(RAM)가 아니라 메모리 중 JVM 에 할당된 메모리를 사용하게 됩니다.

 

자바에서 생성되는 모든 객체는 JVM 의 Heap 메모리를 점유합니다. 해당 객체는 현재 참조 되지 않더라도 메모리 공간을 점유하고, 메모리 공간은 한정적이기 때문에 더이상 쓰지않는 공간을 제때 정리해주지않으면 Out Of Memery Error (OOM) 이 발생하게 됩니다. 이를 해결하기 위해 JVM에는 자동으로 메모리 관리를 해주는 Garbage Collector (쓰레기 수거기)가 존재합니다.


JVM GC 동작 순서

JVM에서 Garbage Collection 은 크게 3가지 step 으로 동작합니다.

  • Heap 영역에 존재하는 객체들에 대해 접근 가능한지 확인한다.
  • GC Root에서 부터 시작하여 참조값을 따라가며 접근 가능한 객체들에 Mark하는 과정을 진행한다.
  • Mark 되지 않은 객체 즉, 접근할 수 없는 객체는 제거(Sweep) 대상이 된고, 해당 객체들을 제거한다.

접근 가능한 객체인지 어떻게 판단할까?

위 그림 처럼 Root에서 시작해서 참조하는 객체를 찾고, 또 그 객체가 참조하는 객체를 찾아가며 Mark 한다. 
Mark 되지 않은 객체는 접근할 수 없는 객체 (Unreachable Object)로 판단하고 메모리를 돌며 제거(Sweep)한다.


Garbage Collection 수행 영역 구분

 

GC 수행 영역 구분

GC는 수행되는 영역에는 크게 물리적 공간이 2개가 있습니다

  • Young Generation 영역 :
    • 새롭게 생성한 객체의 대부분이 이곳에 위치합니다.
    • 대부분의 객체가 금방 접근 불가능 상태가 되기 때문에 대부분의 객체가 Young 영역에 생성되었다가 사라집니다.
    • 이 영역에서 객체가 사라질 때 Minor GC가 발생했다고 말합니다.
  • Old Generation 영역 :
    • Minor GC이후에도 Young 영역에서 사라남은 객체가 여기로 복사됩니다.
    • 대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young 영역보다 GC는 적게 발생합니다.
    • 이 영역에서 객체가 사라질 때 Major GC(혹은 Full GC)가 발생한다고 말합니다.

GC 시나리오

객체가 생성되면 Eden 영역에 위치 하게 된다.
Eden영역이 가득차게 되면 Minor GC가 발생하여 참조가 없는 객체는 삭제되고, 참조 중인 객체는 Survivor 영역으로 이동한다.
Survivor 영역이 가득차게 되면 Minor GC가 발생하고 참조가 없는 객체는 삭제되고, 참조 중인 객체는 다른 Survivor 영역으로 이동한다.
Survivor영역에서의 GC과정을 반복 하며, 계속 참조 중인 객체는 Old Generation으로 이동한다.
Eden 영역에서 Survivor 영역으로 이동 할 때 객체가 남아있는 영역보다 큰 경우 Old Generation으로 이동한다.

*Young Generation 에서 Old Generation으로 이동하는 과정을 promotion 이라고 한다.

 

Young Generation 영역에서 오래동안 살아남은 객체는 Old Generation 영역으로 옮겨지는데, 오래되었다는 기준은 무엇일까?

오래되었다고 하는 기준은 Young Generation 영역에서 Minor GC 가 발생하는 동안 얼마나 오래 살아남았는지로 판단합니다. 각 객체는 Minor GC에서 살아남은 횟수를 기록하는 age bit 를 가지고 있으며, Minor GC가 발생할 때마다 age bit 값은 1씩 증가 하게됩니다. Age bit 값이 MaxTenuringThreshold 라는 설정값을 초과하게 되는 경우 Old Generation 영역을 객체가 이동되는 것입니다. 또는 Age bit가 MaxTenuringThreshold 초과하기 전이라도 Survivor 영역의 메모리가 부족할 경우에는 미리 Old Generation 으로 객체가 옮겨질 수도 있습니다.


Stop the World  (Stop-The-World)

우잠꺼

 

"세상을 멈춘다".. 이 무슨 중2병같은 대사인가.. 하지만 GC에서는 아주 중요한 개념입니다.

Major GC(Full GC)가 발생하면 JVM은 어플리케이션을 멈추고, GC를 실행하는 쓰레드만 동작하게 되는데, 이를 Stop-The-World 라고 합니다. 경우에 따라서는 시스템 장애로 이어 질 수 있기 때문에 GC와는 떼어 놓을 수 없죠.

메모리 공간(heap)을 늘리면 Major GC(Full GC)를 피할 수 있을까요? 메모리 공간을 늘리면 물리적 공간이 그만큼 커지니 Major GC의 첫 수행 시점은 늦출 수 있습니다. 하지만 Stop the World의 시간도 메모리 공간에 비례하기 때문에 Major GC 시간이 그만큼 중가하게 됩니다. 따라서 Stop the World는 절대 피할 수 없고 대신 최적화가 필요합니다.


Mark & Sweep & Compact

 

Mark: 접근 가능한 객체에 Mark하여 표시
Sweep: Mark되지 않은 객체들을 제거하는 과정
Compact: Sweep 과정에 의해 삭제되면 메모리 단편화가 발생하는데, Compact를 통해 빈자리들을 채워줌


Garbage Collector  종류

현재 JVM 에는 5가지 종류의 Garbage Collector 가 있습니다.

  • Serial GC
  • Parallel GC
  • Parallel Old GC
  • CMS(Concurrent Mark Sweep) GC
  • G1 GC
  • Z GC

Serial GC (-XX:+UseSerialGC)

  • 가장 단순한 방식의 GC로 싱글 스레드(스레드 1개)로 동작합니다.
  • 싱글 스레드로 동작하여 느리고, 그만큼 Stop The World 시간이 다른 GC에 비해 깁니다.
  • Mark & Sweep & Compact 알고리즘을 사용합니다.
  • GC가 동작할때 어플리케이션의 모든 쓰레드를 멈추기때문에, 멀티쓰레드 어플리케이션 사용하기에 적합하지 않습니다. 따라서 보통 실무에서 사용하지 않습니다 (디바이스 성능이 안좋아서 CPU 코어가 1개인 경우에만 사용)

Parallel GC (-XX:+UseParallelGC)

  • Java 8의 default GC 입니다.
  • Young 영역의 GC를 멀티 스레드 방식을 사용하기 때문에, Serial GC에 비해 상대적으로 Stop The World 가 짧습니다.

Parallel Old GC (-XX:+UseParallelOldGC / -XX:+ParallelGCThreads=n)

  • Parallel GC는 Young 영역에 대해서만 멀티 스레드 방식을 사용했다면, Parallel Old GC는 Old 영역까지 멀티스레드 방식을 사용합니다
  • -XX:+ParallelGCThreads=n 옵션으로 멀티 스레드 개수를 지정할 수 있습니다.

CMS GC(Concurrent Mark Sweep GC)

  • Stop The World로 Java Application이 멈추는 현상을 줄이고자 만든 GC입니다
  • 접근가능한(Reachable) 객체를 한번에 찾지 않고 나눠서 찾는 방식을 사용합니다 (4 STEP)
    • Initial Mark: GC Root가 참조하는 객체만 마킹 (stop-the-world 발생)
    • Concurrent Mark: 참조하는 객체를 따라가며, 지속적으로 마킹 (stop-the-world 없이 이루어짐)
    • Remark: concurrent mark 과정에서 변경된 사항이 없는지 다시 한번 마킹하며 확정하는 과정(stop-the-world 발생)
    • Concurrent Sweep: 접근할 수 없는 객체를 제거하는 과정 (stop-the-world 없이 이루어짐)
  • 위와 같이 stop-the-world가 최대한 덜 발생하도록 하여, Java Application이 멈추는 현상을 줄입니다.

G1 GC (Garbage First GC) (-XX:+UseG1GC)

  • Java 9+ 의 default GC 입니다
  • 전체 Heap에 대해서 탐색하지 않고 부분적으로 Region 단위로 탐색하여, 각각의 Region에만 GC가 발생합니다.
  • Region의 상태에 따라 그 Region의 역할(Eden, Survivor, Old)가 동적으로 변동합니다
  • G1GC 에는 Full GC 와 유사한 Concurrent Cycle 이라는 과정이 존재하는데요, 해당 과정은 IHOP (InitiatingHeapOccupancyPercent) 에서 정한 수치를 초과하면 실행하게 됩니다
  • 자세한 동작은 다음과 같습니다
    • Initial Mark : Old Region에 존재하는 객체들이 참조하는 Survivor Region을 찾는다(STW)
    • Root Region Scan : 위에서 찾은 Survivor 객체들에 대한 스캔 작업을 실시한다
    • Concurrent Mark : 전체 Heap의 scan 작업을 실시하고, GC 대상 객체가 발견되지 않은 Region은 이후 단계를 제외한다
    • Remark : 애플리케이션을 멈추고(STW) 최종적으로 GC 대상에서 제외할 객체를 식별한다
    • Cleanup : 애플리케이션을 멈추고(STW) 살아있는 객체가 가장 적은 Region에 대한 미사용 객체를 제거한다
    • Copy : GC 대상의 Region이었지만, Cleanup 과정에서 완전히 비워지지 않은 Region의 살아남은 객체들을 새로운 Region(Available/Unused) Region에복사하여 Compaction을 수행한다. 살아있는 객체가 아주 적은 Old 영역에 대해 [GC pause(mixed)] 를 로그로 표시하고, Young GC가 이루어질 때 수집되도록 한다

ZGC (-XX:+UseZGC)

ZGC는 Region을 ZPage로 정의하여 사용합니다. ZPage 는 동적으로 생성/삭제 되며, 2MB의 배수 형태로 관리합니다.

ZGC 개발자는 ZGC가 적은 메모리나 큰 메모리에서 STW 시간을 최대한 적게(10ms 이하로) 가져가기 위해 제작되었다고 합니다

실제로 STW 시간을 줄이기 위해서 Marking 시간에만 STW 을 가져가도록 하고 있습니다.

 

ZGC 의 핵심은 바로 Colored pointersLoad barriers 라는 주요한 2가지 알고리즘입니다.

 

Colored Pointers

객체를 가리키는 변수의 포인터에서 64bit 을 활용해가지고, Marking을 한 것을 볼 수 있습니다. (따라서 ZGC는 반드시 64bit 운영체제에서만 사용가능합니다)

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

Load Barriers

ZGC 는 G1GC 와는 다르게 메모리를 재배치하는 과정에 STW 없이 재배치를 합니다. (위에서 말한 64bit 를 바탕으로)

이때 RemapMark와 RellocationSet을 확인하면서 참조와 Mark를 업데이트하게 됩니다.

 

그래서 ZGC는 아래와 같은 Flow를 따르게 됩니다

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

따라서 G1GC 와의 가장 큰 차이점은, 바로 Pointer를 이용해서 객체를 Marking하고 관리하는 것이라고 볼 수 있습니다!

 

 

출처:
- JAVA의 GC의 종류 및 특징
- GC 튜닝 & STW (STOP-THE-WORLD)
- JVM GC 동작 순서와 GC 종류(Serial / Parallel / CMS / G1 GC ))
- JVM Garbage Collectors)
- JVM과 Garbage Collection - G1GC vs ZGC
728x90