728x90
반응형
ZGC처럼 STW이 유발하는 지연 시간을 줄이는 것이 목표
개념
포워딩 포인터, 배리어 두개를 통해 동시 압축을 구현했다.
- 포워딩 포인터(Forwarding Pointer)
- 모든 객체는 헤더에 1word 크기의 포워딩 포인터를 위한 추가 공간을 가진다. (평상시는 자기 자신을 지칭)
- 1word 는 보통 해당 시스템의 레지스터 크기나 메모리 주소의 크기와 같다.
- 32비트에서는 32비트
- 64비트에서는 64비트
- 1word 는 보통 해당 시스템의 레지스터 크기나 메모리 주소의 크기와 같다.
- 객체의 주소가 변경되면 기존 주소에 있는 객체의 헤더에 변경된 주소 데이터를 남긴다(포워딩 포인터에 변경)
- 모든 객체는 헤더에 1word 크기의 포워딩 포인터를 위한 추가 공간을 가진다. (평상시는 자기 자신을 지칭)
- 읽기/쓰기 배리어
- 이름 그대로 읽기/쓰기 양쪽에 모두 적용되는 배리어
- 애플리케이션 스레드가 힙에 있는 객체에 접근하려 할 때마다 배리어 코드 실행
- 배리어가 객체 헤더를 확인해서 포워딩 포인터가 설정되어 있는지 확인하고, 포워딩 포인터가 있으면 이전 주소 참조를 새 주소로 업데이트한 뒤에 기존 작업 재개
동작
- 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 와 비슷하지만, 그와 비교했을 때 이식성이 높은 대신 처리량이나 메모리 사용량에서 손해를 보는 느낌이다.
반응형
'이론 정리 > java' 카테고리의 다른 글
JDK LTS 와 각각의 내용에 대해 간략히 알아보자(JDK8, JDK11, JDK17, JDK21, JDK25) (0) | 2025.10.10 |
---|---|
간략한 ZGC 방식 설명 (0) | 2025.10.10 |
간략한 G1GC 방식 설명 (0) | 2025.10.10 |
간략한 Serial, Parellal GC 방식 설명 (0) | 2025.10.09 |
모던 자바 인 액션 4장 간략정리 (2) | 2024.11.26 |