이론 정리/java

간략한 Shenandoah GC 방식 설명

철매존 2025. 10. 10. 00:46
728x90
반응형

ZGC처럼 STW이 유발하는 지연 시간을 줄이는 것이 목표

개념

포워딩 포인터, 배리어 두개를 통해 동시 압축을 구현했다.

  • 포워딩 포인터(Forwarding Pointer)
    • 모든 객체는 헤더에 1word 크기의 포워딩 포인터를 위한 추가 공간을 가진다. (평상시는 자기 자신을 지칭)
      • 1word 는 보통 해당 시스템의 레지스터 크기나 메모리 주소의 크기와 같다.
        • 32비트에서는 32비트
        • 64비트에서는 64비트
    • 객체의 주소가 변경되면 기존 주소에 있는 객체의 헤더에 변경된 주소 데이터를 남긴다(포워딩 포인터에 변경)
  • 읽기/쓰기 배리어
    • 이름 그대로 읽기/쓰기 양쪽에 모두 적용되는 배리어
    • 애플리케이션 스레드가 힙에 있는 객체에 접근하려 할 때마다 배리어 코드 실행
      • 배리어가 객체 헤더를 확인해서 포워딩 포인터가 설정되어 있는지 확인하고, 포워딩 포인터가 있으면 이전 주소 참조를 새 주소로 업데이트한 뒤에 기존 작업 재개

동작

  • Init Mark (STW 발생)
    • ZGC 의 Pause Mark Start 를 생각하면 된다. GC root 에서 직접 참조하는 객체들을 마킹해 준다.
  • Concurrent Marking (STW 없음)
    • 위의 Init Mark 에서 마킹된 객체부터 참조하는 모든 객체를 다 마킹해 준다. 동시에 압축할 Region 을 선정한다. (살아남는 객체가 적은 것들을 마킹)
  • Final Mark (STW 발생)
    • 새로 생성되거나 참조가 변경된 객체들을 최종적으로 처리
    • G1GC 의 remark 와 비슷한 역할을 하고, 위에서 대상 객체들을 이미 줄여뒀기 때문에 빠르게 진행된다.
  • Concurrent Evacuation (STW 없음)
    • 이게 Shenandoah 의 핵심이다.
    • 압축 대상인 Region 에 있는 살아있는 객체를 새로운 Region 으로 복사한다.
    • 여기서 포워딩 포인터와 읽기/쓰기 배리어 활용
      • 객체가 새 위치로 복사되면 이전 위치 객체의 헤더에 새 주소를 가리키는 포워딩 포인터 설정
      • 그래서 옛 주소로 접근하면 배리어가 이를 감지해서 참조를 새 주소로 고쳐준 후 잡업 재개
  • Concurrent Update References (STW 없음)
    • ZGC 의 Concurrent Remap 을 생각하면 된다.
    • Concurrent Evacuation 에서 객체 이동이 완료되면 힙 전체를 스캔해서 이전 주소를 가리키는 참조를 모두 새 주소로 업데이트해 준다.

장점

  • 객체 이동 단계에서 STW 가 일어나지 않는다.
    • 이 때 가장 긴 시간 STW가 일어나는데 얘는 그게 없으므로 일시정지 시간이 매우 짧다.
  • 64비트가 아니어도 괜찮다. (ZGC 대비 장점)

단점

  • 처리량 감소
    • 배리어가 참조를 읽을 때 마다 실행되기 때문에 CPU 오버헤드가 지속적으로 발생한다. 심지어 이거는 읽기/쓰기 단계에서 모두 발생해서 ZGC보다도 더 많이 처리량이 감소할 수 있다.
  • CPU, 메모리 사용량 증가
    • GC 과정에서 계속 백그라운드 스레드를 사용할 수밖에 없다.
    • 힙의 모든 객체가 1word 추가 공간을 헤더가 가지고 있어야 하기 때문에 메모리 오버헤드가 늘어난다.

ZGC 와 비슷하지만, 그와 비교했을 때 이식성이 높은 대신 처리량이나 메모리 사용량에서 손해를 보는 느낌이다.

반응형