Post

[Java] Garbage Collector

[Java] Garbage Collector

📌 GC(Garbage Collector)란?

Garbage Collector란 JVM이 Heap 영역에서 더 이상 사용되지 않는 객체를 자동으로 탐지하고 해당 메모리를 회수하는 메모리 관리 기능을 의미한다. 따라서 개발자가 직접 메모리 해제를 하지 않아도 된다.

📌 JVM에서 Heap 메모리 구조

image.png

  1. Young Generation
    • Eden Space: 새로 생성된 객체는 대부분 이 공간에 저장된다.
    • Survivor Spaces: Eden Space에서 살아남은 객체가 저장되는 공간이다.
    • Young Generatiob에서는 객체의 생성과 소멸이 빈번하게 발생하며 Minor GC 가 자주 발생한다.
  2. Old Generation
    • Young Generation에서 여러 번의 Minor GC를 거쳐 살아남은 객체가 저장되는 공간이다.
    • Young Generation보다 크기가 크며 Major GC 가 발생하면 이 공간의 객체가 소멸된다.
  3. Permanent Generation
    • Java 8 이전에 존재하던 공간이다. 클래스의 메타데이터와 static 변수가 저장되었다. Java 8부터 Metaspace 라는 별도의 네이티브 메모리 공간으로 대체되었다.
    • 이 공간은 힙이 아니다.

📌 Major GC와 Minor GC

위에서 등장한 Major GC와 Minor GC에 대해 더 자세히 알아보자.

Major GC

Major GC는 Old Generation에서 발생한다. Old Generation의 메모리가 부족할 때 트리거된다. 전체 힙 또는 Old Generation을 대상으로 하므로 Minor GC에 비해 수행 시간이 길고, 애플리케이션의 중단 시간이 더 길다. 애플리케이션의 중단 현상을 Stop the World 현상이라고 하는데, 뒤에 후술한다.

Minor GC

Minor GC는 Young Generation에서 발생한다. Eden 공간의 모든 객체를 검사하여 참조되지 않은 객체를 제거하고 살아남은 객체는 Survivor 공간으로 이동시킨다. Survivor 공간이 가득 차면 살아남은 객체는 다른 Survivor 공간으로 이동하거나 여러 번의 Minor GC를 살아남은 경우 Old Generation으로 승격된다. Younger Generation을 대상으로 하므로 수행 시간이 짧고 Stop-the-World 현상이 발생하나 그 영향이 크지 않다.

표로 정리하면 다음과 같다.

구분대상 공간트리거 조건수행 시간영향
Minor GCYounge GenerationEden 영역이 가득 찼을 때짧음영향 적음
Major GCOld GenerationOld 영역이 가득 찼을 때시스템 성능에 영향 큼

📌 동작 방식

Stop the World

Stop the World는 GC를 실행하기 위해 JVM이 애플리케이션의 모든 쓰레드를 일시적으로 중지시키는 현상이다. GC 작업이 끝나면 다시 애플리케이션이 실행된다.

모든 쓰레드를 멈추는 이유가 무엇일까? 메모리 상에서 객체의 참조 관계가 변하지 않도록 보장해 살아있는 객체와 죽은 객체를 정확하게 식별하기 위함이다. GC가 실행되는 동안 객체 참조가 변경된다고 생각해보자. 올바른 객체 식별이 어려울 것이다.

Stop the World는 GC의 성능과 직결되며, 이 단계의 시간을 최소화하기 위해 GC 튜닝을 수행하기도 한다. 최신 GC 알고리즘은 Stop the World 시간을 줄이기 위해 대부분의 작업을 애플리케이션이 실행됨과 동시에 처리하고 꼭 필요한 순간에만 짧게 Stop the World를 발생시키도록 설계되어 있다.

Mark and Sweep

Mark and Sweep은 MarkSweep 단계로 나뉜다.

Mark 단계에서 우선 모든 객체의 mark bit 를 초기화한다. GC Root부터 참조를 따라가며 살아있는 객체를 탐색하고, 탐색 과정에서 살아있는 객체의 mark bit를 설정한다. 탐색이 완료되면, mark bit가 설정된 객체는 살아있는 객체, 그렇지 않은 객체는 죽은 객체가 된다.

GC root는 GC에서 객체의 생존 여부를 판단하기 위한 기준점이 되는 객체 집합이다.

Sweep 단계에서 GC는 힙 전체를 다시 순회하여 mark bit가 설정되지 않은 즉, 죽은 객체를 메모리에서 제거한다.

Mark and Sweep 방법은 구현이 단순하고 효과적이나 Sweep 단계 이후 메모리의 빈 공간이 불규칙하게 남아 메모리 단편화가 발생할 수 있다. 이를 해결하기 위해 일부 GC 알고리즘은 Sweep 단계 이후 Compaction 단계를 추가하여 연속된 여유 공간을 확보하기도 한다.

📌 GC의 알고리즘

Serial GC

단일 쓰레드에서 모든 GC 작업을 수행한다. Stop the World 방식을 사용하므로 쓰레드가 멈추는 시간이 길 수 있다. CPU 오버헤드가 적고 구현이 간단하며 예측 가능한 동작을 보장하지만 멀티쓰레드 환경이나 규모가 큰 애플리케이션 환경에는 부적합하다.

Parallel GC

Serial GC와 달리 여러 쓰레드를 활용하여 Young/Old Generation에서의 GC 작업을 병렬로 처리한다. 따라서 GC의 오버헤드를 의미 있게 줄일 수 있다.

Parallel Old GC

Old Generation 전용 Parallel GC이다.

CMS(Concurrent Mark Sweep) GC

웅답 속도가 중요한 애플리케이션을 위해 설계된 low latancy GC이다. Stop the World 시간을 최소화하는 것이 주요 목표이며, Mark and Sweep 작업을 애플리케이션 쓰레드와 동시에 진행하여 쓰레드가 멈추는 시간을 줄인다. 그러나 CPU 사용량이 놓고 메모리 단편화 문제가 발생할 수 있다.

G1(Garbage First) GC

대용량 메모리와 멀티프로세서 환경을 위해 설계된 서버용 GC이다. 힙을 수백 개의 작은 공간으로 나누고, 각 공간의 garbage 비율을 추적하여 많은 garbage가 있는 공간부터 처리한다. 대부분의 작업을 parellel 및 concurrent하게 처리한다.

Shenandoah

규모가 작은 GC 작업을 자주 사용하여 쓰레드가 멈추는 시간을 일정하게 유지하는 것이 목표이다. G1 GC와 유사하기 힙을 여러 공간으로 나누지만 generation을 구분하여 나누지 않는다.

ZGC

최대 16TB의 초대용량 힙에서도 매우 짧은 쓰레드 정지 시간을 보장하는 low latency GC이다. CPU 자원을 더 많이 사용하지만 쓰레드 정지 시간이 극도로 짧다. 더 발전된 GC인 Generational GC는 Young, Old Generation을 논리적으로 분리하여 효율성을 높인다.

📌 참고

https://velog.io/@recordsbeat/Garbage-Collector-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%95%8C%EA%B8%B0

https://mangkyu.tistory.com/118

https://mangkyu.tistory.com/119

This post is licensed under CC BY 4.0 by the author.