GC 란?

4 분 소요

업데이트:


GC란?

  • Garbage Collection.
  • GC란 JVM의 Heap 영역에서 사용하지 않는 객체를 삭제하는 작업을 말한다.
  • GC는 다음 두가지 가설 하에 만들어졌다. (weak generational hypothesis)
    1. 대부분의 객체는 금방 접근 불가능 상태(unreachable)가 된다.
    2. 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.


GC 작업

  • heap 내의 객체 중 가비지 (garbage)를 찾는다.
  • 찾아낸 가비지를 삭제해서 힙의 메모리를 회수한다.


GC와 unreachable

  • GC의 수거 대상은 heap 영역에 있는 unreachable한 객체. 즉, 어떠한 참조도 갖고 있지 않은 객체.
  • GC Root로부터 각각 어떤 객체를 참조하고 있는지 스캔해서 unreachable한 객체를 수거.


GC Root

  • runtime data area는 다음과 같이 쓰레드가 차지하는 영역들과 , 클래스 정보가 들어있는 메서드 영역 등으로 나눌 수 있다.

  • 또한, 다음과 같이 힙에 있는 객체들에 대한 참조는 다음 4가지 중 하나이다.
    • 힙 내의 다른 객체에 의한 참조
    • Java 스택 프레임 내에 저장되는 지역변수와 파라미터에 의한 참조
    • JNI에 의해 생성된 객체에 대한 네이티브 스택 내에서의 참조
    • 메서드 영역의 static 변수에 의한 참조

  • 이중에서 Java stack 내의 변수, JNI에 의해 생성된 객체에 대한 참조, 메서드 영역의 static 변수가 GC Root가 될 수 있다.
  • 그리고 GC Root에 의해 힙 내의 객체들에 대한 reachability가 결정된다.
  • 만약, GC Root에 대한 참조가 없는 객체는 unreachable하다고 표현하며 GC의 수거 대상이 된다.


Stop the world

  • gc를 수행시킬 때, gc를 수행하는 쓰레드를 제외한 모든 쓰레드가 정지되는 현상을 가리켜 Stop the world 라고 한다.
  • stop the world는 gc 성능을 결정짓는 중요 요소이기 때문에 gc 튜닝을 한다라고 하면 대부분 stop the world 시간을 줄이는 것이 관건이 된다.


GC 동작과정

GC 세부 동작과정

  • GC 동작과정을 알기전에 먼저 heap의 내부구조에 대해 알아야 한다.

  • heap은 다음과 같이 크게 young gen, old gen으로 나뉜다.
  • young gen은 다시 eden과 두개의 survivor로 나뉜다.
  • heap 영역을 나누는 이유는 heap 전체를 탐색하여 메모리를 해제하는 full gc로 인한 성능상의 이슈를 최소화 시키기 위함이다.
    • weak generational hypothesis 가설의 장점을 극대화 시키기 위함.

  • 가장 최근에 생성된 객체들은 위와같이 eden 영역에 쌓인다.

  • eden 영역이 가득차면, eden 영역을 대상으로 GC가 수행되며 이때 GC는 mark and sweep 알고리즘을 사용해 가비지를 수거한다.
    • gc root를 스캔하면서 unreachable한 객체를 식별하는 과정을 mark, unreachable한 객체를 삭제하는 과정을 sweep이라고 부른다.
    • reachable한 객체에는 mark bit를 1(true)로, unreachable한 객체에는 mark bit를 0(false)로 지정.

    • 그리고 이때, eden 영역을 대상으로 수행된 GC를 minor gc라고 부른다.
  • 이후, eden영역을 대상으로 minor gc가 수행되고 살아남은 객체들은 다음과 같이 survivor 영역으로 복제된다.

  • 이때, 두개의 survivor 영역에 객체가 공존할 수 없다는 규칙이 있다.
    • 즉, survivor 영역 중 하나는 반드시 비어있는 상태여야한다.
    • survivor 영역을 두개로 나누는 이유는 메모리 단편화 문제를 해결하기 위함이다.
  • 위의 모든 과정들을 반복하면서 survivor 영역이 가득차면 young gen을 대상으로 minor gc가 수행된다.
  • 이때, 살아남은 survivor 객체들은 다른 survivor 영역으로 복제되고, 가득찼던 survivor 영역은 모두 비운다.
  • 이 과정을 수행하면서, 특정 임계점을 지날때동안 살아남은 survivor 내의 객체는 old gen으로 복제된다.
    • 이 과정을 가리켜 promotion이라고 한다.

  • 위의 과정들이 반복되면서 old gen이 가득차면 old gen을 대상으로 gc를 수행하는데, 이를 major gc라고 한다.
    • major gc는 minor gc에 비해 스캔 대상이 많고 수행하는데 시간이 오래걸린다.
    • major gc는 minor gc에 비해 수행 빈도가 적다.

트래픽이 무지많이 몰리는 이벤트가 예정되어있어, 트래픽이 많을때 young gen과 old gen 비율은 어떻게 하는 것이 좋을까?

  • 트래픽이 많이 몰린다면 객체 생성도 그만큼 많이 될 것.
  • 다만, 이러한 많은 객체들 중 수명이 짧은 객체들이 대부분일 것으로 예상됨.
  • 이러한 상황에서, young gen이 차지하는 비율이 작다면 그만큼 minor gc의 빈도가 늘어날 것이고, minor gc에 의해 발생하는 stop the world의 빈도가 많아져 성능에 악영향을 줄 것임.
  • 그렇다면, minor gc에 의한 stop the world의 빈도 수를 줄이기 위해, young gen이 차지하는 비율을 높이면 어떻게 될까?
  • minor gc에 의한 stop the world의 빈도수를 줄일 수 있겠지만, 해당 stop the world를 한번 수행할 때마다 그만큼 young gen에 객체가 많이 저장되기 때문에, 객체를 수거하는데 걸리는 시간은 이전보다 오래걸릴 것이다.
    • 시간이 적게 걸리지만 많이 수행하느냐 vs 시간이 오래걸리지만 한번만 수행하느냐에 대한 차이라고 볼 수 있다.
    • 여기서는 stop the world 빈도를 줄이는게 맞는 선택이라고 생각한다.
      • stop the world는 gc를 동작시키는 thread를 제외한 나머지 모든 thread를 all stop 하는 엄청난 비용을 소모하는 작업이기 때문이다.
  • 하지만, old gen이 차지하는 비율도 상대적으로 작아져 old gen을 대상으로 하는 major gc의 빈도도 많아질 것이다.
  • 결국, young gen을 대상으로 한 minor gc 의 빈도수 vs old gen을 대상으로한 major gc의 빈도 수 사이의 trade off가 발생하는 상황이라고 생각한다.
  • 다만, old gen은 young gen과 같이 eden이나 survivor space가 존재하는 게 아니기 때문에 메모리 단편화도 신경써야하고, 일반적으로 youn gen보다 더 많은 객체들을 관리하다 보니 major gc의 빈도가 높아지면 성능에 좋지 않은 역할을 줄 것은 분명하다.
  • 그렇기 때문에, old gen의 비율을 줄여가면서까지, minor gc의 빈도를 줄이기 위해 young gen의 비율을 높이는 것이 위험한 선택이 될 수 있을 것이라 생각한다.
  • 결론적으로… 오직 트래픽이 많을것이라는 가정만을 가지고 young gen과 old gen의 최적화된 비율을 수치화 하기는 어렵지 않을까? 라고 생각한다.
  • 각 애플리케이션마다 저마다의 특징을 갖고있고, 애플리케이션을 동작시키는 환경도 제각각이어서, 여러가지 변수들이 존재할 것이기 때문이다.
  • 그렇기 때문에 각 애플리케이션마다 대용량 트래픽에 대한 성능 테스팅이나 모니터링 과정을 통해 young gen과 old gen에 대한 최적의 비율을 찾는 것이 중요할 것이라 생각한다.


GC의 종류

Serial GC

  • 가장 단순한 방식의 GC로 싱글 스레드로 GC를 동작시킨다.
  • 싱글 스레드로 동작하여 느리고, 그만큼 STW 시간이 다른 GC에 비해 길다.
  • Mark & Sweep & Compact 알고리즘을 사용한다.
  • 보통 실무에서 사용하는 경우는 없다.

Parallel GC

  • GC 작업을 여러 스레드로 병렬처리하여, STW 시간을 최소화 시키기 위한 GC이다.
  • young gen에 대한 minor GC를 병렬처리 한다.
  • Parallel Old GC라는 것도 있는데, 해당 GC는 old gen에서 수행되는 major GC도 병렬처리 한다.
  • Parallel GC는 자바 8의 디폴트 GC 이다.

CMS GC(Concurrent Mark Sweep GC)

  • STW로 인해 Java Application이 멈추는 현상을 줄이고자 만든 GC이다.
  • 가비지 수거 후 compaction을 수행하지 않는다.
  • reachable한 객체를 한번에 찾지 않고 4단계로 나눠서 찾는 방식을 사용한다.
    • Initial Mark
      • GC Root가 참조하는 객체만 마킹한다.
      • 이 과정에서 stop-the-world 발생한다.
    • Concurrent Mark
      • 이후의 참조하는 객체를 따라가며, 지속적으로 마킹한다.
      • stop-the-world 없이 이루어진다.
    • Remark
      • concurrent mark 과정에서 변경된 사항이 없는지 다시 한번 마킹하며 확정하는 과정이다.
      • stop-the-world가 발생한다.
    • Concurrent Sweep
      • unrechable한 객체를 제거하는 과정이다.
      • stop-the-world 없이 이루어진다.

G1GC

  • Garbage first Garbage Collector 이다.
  • Eden, Survivor, Old 영역을 고정된 크기로 고정된 위치에 나누는 것이 아니라, 전체 힙 메모리 영역을 리전이라는 일정한 크기로 나눠서 각 리전의 상태에 따라 그 리전의 역할이 동적으로 부여되는 방식으로 동작한다.
  • 전체 힙에 대해서 탐색하지 않고 부분적으로 리젼 단위로 탐색하며, 각각의 리젼에만 GC가 발생해 빠르게 GC를 수행할 수 있다.
  • 자바 9부터 디폴트 GC 이다.


참고

댓글남기기