저지연 컬랙터 - 셰넌도어, ZGC

블러거·2026년 1월 19일

JVM

목록 보기
10/26

GC 는 처리량, 메모리 사용량, 지연 시간 세가지 지표가 있다. 처리량과 메모리 사용량은 하드웨어가 발전하며 좋은 지표를 내기 쉽지만, 지연 시간은 그렇지 않다. 더 많은 용량의 RAM 이 개발될 수록 GC 를 위한 시간은 점점 더 멀어진다.

따라서 GC 는 지연 시간을 줄이는 방향으로 발전했다.

G1 GC 은 Young GC 및 Mixed GC 에서 생존한 객체를 다른 Region 으로 옮기는 소위 Compact 라 불리는 작업은 STW 로 동작한다.
저지연 컬렉터는 Compact 를 유저 스레드와 동시에 진행하여 아주 낮은 일시 정지로 낮은 지연 시간을 달성하는 컬렉터이다.

처리량 영향을 최소로 하면서 10ms 이하의 일시 정지 시간이 목표다.

Shenandoah

개요

섀넌도어는 레드햇에서 만든 저지연 컬렉터이다. 포워딩 포인터 기법을 이용하여 Compact 를 동시에 처리한다.

Oracle JDK 에서는 제공하지 않으므로, Oracle JDK 를 유료로 지원받을 계획이 있다면 사용하지 않는게 좋다.

세대구분 셰넌도어도 존재하나, 처음에는 세대구분이 없었다. 세대 구분 없는 셰넌도어 기준으로 설명한다.

GC Cycle


https://wiki.openjdk.org/spaces/shenandoah/pages/25002018/Main?utm_source=chatgpt.com

OpenJDK 문서에 따르면 셰넌도어는 9가지 단계로 이뤄진다.

  1. 최초 표시 : GC Root 를 스캔 마킹
  2. 동시 표시 : 마킹된 GC Root 로 부터 동시 마킹, 변경이 발생하면 SATB 큐에 기존 값 저장
  3. 최종 표시 : 동시 마킹시 발생한 참조 변경(SATB 큐)에 의해 발생한 마킹을 시작 스냅샷 기준으로 수정하기 위한 최종 작업과 회수 집합을 생성
  4. 동시 청소 : 빈 리전 회수
  5. 동시 이주 : 마킹시 생존한 객체를 유저 스레드와 동시에 다른 리전으로 복사하며 Compact
  6. 최초 참조 갱신 : 동시 참조 갱신을 위한 준비 - 동시 이주 작업이 모두 끝나길 기다리는 벽 역할을 하는 STW
  7. 동시 참조 갱신 : 생존한 객체를 참조하는 곳을 변경.
    • 기존 마킹을 진행한 객체 참조에서 복사/이주 한 객체 참조로 변경.
    • GC Root 에서 객체 참조를 따라가는 방식이 아닌, 힙 메모리를 물리적인 순서대로 선형 검색하며 변경.
  8. 최종 참조 갱신 : GC Root 의 참조도 복사/이주 한 객체 참조로 변경. GC Root 이기에 STW.
  9. 동시 청소 : 4번과 같은 역할. 참조 갱신까지 완료된 후 빈 영역 회수

포워딩 포인터와 barrier

메모리 파편화를 피하기 위해서는 Compact 과정이 불가피하다. 즉, 객체가 다른 위치로 이동해야 한다는 것이다.

이 때 고려해야할 것은 두가지다.
첫번째로, 객체를 이동할 때 복사를 하고 기존 객체를 나중에 정리할 것이냐 or 새 위치에 새로운 객체를 생성하면서 기존 객체를 삭제 할 것이냐다.
언뜻 보면 객체를 두벌 관리하여 공간을 차지하는 복사보단 삭제후 생성이 좋아 보인다. 하지만 삭제 - 생성하게 되면 기존 객체가 사라지니 반드시 기존 객체에 대한 참조를 새로운 객체 참조로 변경하는 것도 같이 발생해야 한다. 하지만 특정한 하나의 객체를 새로 생성할 때 해당 객체를 사용하는 곳을 모두 찾아 참조를 변경하는 것은 STW 에서 일어나야 하며, 이는 시간이 많이 걸린다. 따라서 두벌을 유지하는 복사 이주 방식을 택한다.

두번째로, 그럼 복사 이동을 택했을 때 객체를 참조하는 곳의 참조값을 이전 참조값에서 새 참조값으로 변경하는 방법이다. 이는 아래에서 자세히 설명하겠다.

동시 복사/이주의 문제


https://shipilev.net/talks/javazone-Sep2018-shenandoah.pdf

위 그림은 7번 동시 참조 갱신 중의 살아남은 객체의 상태다.
객체는 동시 이주 단계를 거쳐 두벌로 복사된 상태이다.
검정 네모는 GC Root 이며, 객체를 참조하는 GC Root 는 총 4개가 있다.

동시 참조 갱신 단계 중 2개의 GC Root 참조는 이주/복사된 새 객체를 참조하도록 갱신되었으나, 2개의 GC Root 참조는 아직 이주/복사 이전 객체를 참조한다. 이러한 타이밍은 반드시 있다.

이 때 만약 예를들어 참조를 옮기지 못한 왼쪽의 GC Root 를 통해 값의 수정이 이뤄지고(y=2 -> 4), 참조 갱신이 끝난 후 값을 조회한다면 수정되지 않은(y=2) 의 값을 읽어버릴 수 있다.

포워딩 포인터와 barrier

이를 위해 컬러 포인터 개념이 있다.


포워딩 포인터는 위와 같이 기존 객체 layout 앞에 추가된 포인터이다.
객체 레퍼런스를 가지고 있으며, 시스템에 따라 32/64 bits 이다.

이 포인터는 처음에는 위 그림처럼 자기 자신 객체 참조 주소를 가진다.


복사시점의 상태다. from-space(기존 객체) 와 to-space(복사/이동 된 객체) 의 포워딩 포인터는 모두 자기 자신을 가리킨다.


복사가 완료되면 from-space 포워딩 포인터를 to-space 를 가리키도록 변경한다.
이 동작에는 동시성 문제가 있는데,

  1. 복사
  2. 유저 스레드 의 기존 참조 수정
  3. from-space 포워딩 포인터 변경

위와 같이 복사 후 포워딩 포인터를 옮기기 전 mutator 스레드의 객체 참조가 발생하면 from-space 객체를 볼 수 있다.
따라서 쓰는 동작에서 CAS 를 이용한 write barrier 가 존재하여 to-space 객체에 쓸 수 있도록 동작한다.


이 그림은 참조 갱신 중에도 정상적으로 포워딩 포인터를 통해 to-space 의 객체를 수정하는 예시다.

모든 참조 갱신이 완료되면 기존 from-space 를 정리할 수 있다.

LoadReferenceBarrier

셰넌도어 1.0 에서는 두벌로 존재하는 객체에서 힙 일관성을 지키기 위해 모든 참조에 대해 Write/Read 배리어를 건다.


https://developers.redhat.com/blog/2019/06/27/shenandoah-gc-in-jdk-13-part-1-load-reference-barriers?p=602377

위의 코드에서 value 객체가 primitive type 이라도 barrier 를 건다.
barrier 를 쓰는 이유는 참조 타입 객체를 읽거나 쓸 때 해당 참조 타입 객체가 이동되는걸 방지하기 위한 것이라서, primitive type 은 사실 barrier 를 걸지 않아도 된다.

LoadReferenceBarrier 의 탄생으로 객체를 읽을때 to-space 에서 읽는것을 보장함으로서 barrier 의 수를 많이 줄일 수 있었다.

포워딩 포인터 객체 헤더 통합

포워딩 포인터는 태초에 위처럼 마크 워드 앞에 자리를 차지했다.
마크 워드는 최하위 2비트의 플래그 비트로 여러 데이터를 표현할 수 있었는데, 그 자리에 뒀으면 안됐을까?

기존의 섀넌도어에서는 모든 객체 접근에서 to-space 를 보기 위해 마크워드 디코딩 “매번” 실행하여 검사하는 hotpath가 되어버려서 비용이 너무 컸다.

Load Reference Barrier 의 등장으로 포워딩 포인터는 mark-word 에 병합되었다.

  1. Barrier 가 모든 객체, 특히 primitive 접근에 필요하지 않다.
    • 복사가 되었는지 확인하기 위해 포워딩 포인터를 확인하는 횟수가 많이 줄어 디코딩 비용이 줄었다.
  2. from-space 객체에 더이상 접근하지 않는다.
    • from-space 의 내용을 모두 지우고, 포워딩 포인터 데이터만 남긴다.
    • 복사되어 포워딩해야 하는가, 아직 복사 안되어 포워딩 안해도 되는가의 2가지만 디코딩 하면 되어 디코딩 비용이 줄었다.

이로 인해 메모리 사용량이 많이 줄게 되었다.

https://developers.redhat.com/blog/2019/06/28/shenandoah-gc-in-jdk-13-part-2-eliminating-the-forward-pointer-word?utm_source=chatgpt.com

ZGC

개요

ZGC 는 오라클에서 주도로 만든 저지연 컬렉터이다. 마찬가지로 Compact 를 유저 스레드와 동시에 진행하여 저지연을 달성한다.

Oracle 문서를 보면 다음과 같이 표현되어 있다.
Scalable Low-Latency Garbage Collector
Scalable 이란, 힙의 크기나 객체의 양이 많아져도 여전히 낮은 일시 정지 시간과 높은 처리량을 낼 수 있다는 말이다.

세대구분 ZGC도 존재하나, 처음에는 세대구분이 없었다. 세대 구분 없는 ZGC 기준으로 설명한다.

Page

ZGC 는 메모리를 Page 로 관리한다. (G1 의 리전과 비슷)
G1 과 다르게 ZGC 의 Page 는 소/중/대 세가지가 존재한다.

  • 소 : 2MB 고정 크기. 256KB 미만의 작은 객체 저장용
  • 중 : 32MB 고정 크기. 256KB 이상 4MB 이하 객체 저장용
  • 대 : 크기가 동적으로 변할 수 있음. 4MB 이상의 큰 객체 저장용.
    • 대는 큰 객체 하나만 저장. 따라서 32MB 를 담을 수 있는 중리전보다 가지고 있는 실제 객체크기가 적을 수 있음.
    • 큰 객체의 복사 비용이 높기에, 재배치의 대상이 되지 않음.

Page 는 동적으로 생성/파괴되며, 동적으로 바뀔 수 있다.

GC Cycle

1. STW1 : Start Of The ZGC

이 단계에서는 크게 세가지 일을 한다.

  1. Good Color 를 001(marked0) 또는 010(marked1) 중 선택한다. 전 사이클에 사용되지 않은 것이 선택된다.
  2. 동시 마킹 중 새로 생성된 객체를 할당할 Page 를 생성한다. 이 Page 에 있는 객체는 모두 이번 사이클에서 Marked 로 간주하여 Mark Stack 에 적재한다.
  3. GC Root 를 마킹 : GC Root 에 Mark Barrier 가 적용된다.

2. Concurrent Marking/Remapping


위는 마킹 루프 스켈레톤 코드다. 마크 스택의 객체를 pop 하여 mark_obj(bitmap 에 마킹)하고, MB(Marking Barrier) 로직을 실행한다.
아래는 마킹 배리어(MB) 는 마킹 단계에서 컬러포인터를 확인하는 과정, 로드 베리어(LB) mutate thread 의 객체 참조 과정에서 컬러포인터를 확인하는 과정을 보여준다.

이 단계에서는 객체 마킹과 이전 사이클 Relocation 객체의 Remapping 을 동시에 진행한다.

위 marking loop 스켈레톤 코드가 이 단계에서 진행하는 내용을 간략화 한 것이다. mark_obj() 함수에서는 bitmap 에 살아있음을 마킹하고, 이후에 해당 객체의 필드들을 순회하며 Mark Barrier 를 적용한다.

MB 에서는 컬러포인터를 확인하고 EC 에 포함되는 객체라면 Remapping, 아니면 컬러포인터 수정을 한 뒤 Marking Stack 에 쌓는다.

이 단계에서 사용되는 EC 는 이전 Cycle 에서 만들어진 EC

이 동작의 반복으로 살아있는 객체 그래프를 순회하며 Marking 과 Remapping 을 동시에 처리하게 된다. 그리고 그 첫 시작은 STW1 에서 Stack 에 넣은 GC Root 일 것이다.

따라서, 이 단계가 진행된 후에는 Dangling(Relocation 이전 주소를 가진 참조) 가 없음이 보장된다.

Mutator Thread 의 객체 참조시 사용되는 LB 와 다른점은 두가지다.

  1. 마킹에 사용되므로 반환값이 void 인 것
  2. 모든 객체 그래프를 순회하면서 마킹해야하므로 컬러포인터가 정상이더라도 fast path 없이 모두 slow path 적용하여 marking stack 에 집어넣는것

2번 때문에 같은 객체가 중복으로 Marking Stack 에 들어가는 부작용이 있지만, Bitmap Marking 동작은 CAS 로 Thread-Safe 하므로 문제는 없으며, Mutator Thread 의 Stack 에는 할일이 많지만 GC Thread 가 할일이 없어 Marking/Remapping Phase 가 끝남을 방지하기 위함이다. (G1 과 같이 이것도 Mutator Thread 에 로컬 스택이 있고, 글로벌 스택이 있는듯)

또한, ZGC 는 incremet-update 방식(Not SATB) 을 사용한다. Mutator Thread 에 의해 접근된 객체는 LB 로 Marking Stack 에 적재되며 동작한다.

마킹에 Mark0, Mark1 비트 두개가 사용되는 이유는 전 사이클의 Remapping 과 현 사이클의 Marking 이 동시에 진행되기 때문이다.
예를들어 전 사이클에서 마킹되었지만 Relocation 은 되지 않은 객체가 있다고 가정하자. 현 Marking/Remapping 에서 Çolor Pointer 는 0001. 현 사이클에서도 살아있는 객체라 새로 0010 으로 마킹 될 예정이다. 하지만 마킹이 진행되기 전에 Mutator Thread 로 부터 참조 조회가 있었고, LB 가 진행되는 상황이라 치자. 이 때 만약 Marking 을 나타내는 필드가 하나라면, 이전 사이클에는 마킹했으나 현 사이클에는 아직 마킹을 하지 않아서 이번 사이클의 good color 로 색칠해야함 을 표현할 수 없다. 비트가 하나라면 그냥 1 일 것이고 이번 사이클에서 마킹한 것인지 전 사이클에서 마킹한 것인지 구분할 수 없다는 것이다.
이는 결국 Marking 과 Remapping 을 동시에 진행함으로서 발생된 것이며, Marking 을 표현하는데 두 비트를 사용하는 것은 결국 성능상의 트레이드 오프라 할 수 있다.

3. STW2 : End of the Marking Phase

Mark Stack 이 비어 모든 객체가 마킹 되길 기다리는 STW

4. Reference Processing

이 단계는 Soft, Weak, Phantom Reference 를 처리하는 단계인데, 복잡하여 일단 생략

5. Selection Of Evacuation Candidate

Evacuation Candidate (또는 Relocation Set) 은 살아남은 객체가 많지 않아 살아남은 객체를 다른 곳으로 이주시키고 해당 Page 를 회수할 Page 의 집합이다.

이 단계에서는 몇가지 정보를 삭제한다.

  1. 이전 Cycle 의 EC 집합을 삭제
  2. 이전 Cycle EC Page 의 Fowarding 테이블 삭제 : Marking 단계가 지나며 Dangling Reference 가 없음이 보장되었음이 근거
  3. 살아있는 객체가 하나도 없는 Page 회수

그리고 EC 를 만든다. 먼저 이번 GC Cycle 에서 새로 생성된 객체를 넣기 위해 STW1 에서 생성한 Page 를 제외한 모든 Page 중, 살아있는 객체가 적은 순으로 정렬(살아있는 객체 집계값은 marking 에서 생성)한 후, 파편화 임계값에 따라 Relocation 대상 Page 를 선정하여 EC 를 완성한다.

큰 Page 는 Relocation 대상이 아니다. EC 에는 소, 중 Page 만 포함

STW3 : Transitioning to Relocation

  1. Good Color 를 0100(Remapped) 로 변경한다.
  2. GC Root 가 EC 를 바라본다면, 해당 EC 를 relocation 하고 그렇지 않다면 단순 color 를 0100 으로 수정한다.
    • GC Root 작업을 하기에 STW


Mark0/1 과 Remapping 의 Load Barrier 차이
4번줄에 EC 로 변수는 같으나 Mark 단계 LB 의 EC 는 이전 Cycle EC 이고, Remapped 단계 EC 는 Selection of Evacuation Candidate 에서 선정한 EC 인 점이 다르다.
또한, EC 에 포함되었을때 왼쪽은 단순 remapping 이지만 오른쪽은 relocation 을 한다.

Relocation

이 단계의 스켈레톤 코드는 위와 같다.
EC Page 의 객체들을 Relocation 하면서 Fowarding 테이블에 저장한다. Fowrading 테이블을 이후 LB, MB 에서 사용된다.

Relocation 이 끝난 Page 는 메모리에서 해제한다.

이 단계가 끝나서 객체가 모두 이주하더라도 여전히 이전 객체 참조값을 가진 곳이 있을 수 있다. 컬러포인터 + Barrier + Fowarding Table 덕에 이는 급하게 처리할 일은 아니며, 다음 사이클의 Marking/Remapping 단계에서 빠짐없이 처리될 것이다.

컬러포인터

ZGC 는 동시 이주를 실현하기 위한 기술

컬러포인터는 객체 참조 주소값 자체에 포함되는 4비트의 메타데이터로서, GC 와 동시에 실행되는 유저의 객체 참조시 참조가 이동했더라도 정상적으로 이동한 참조를 찾아갈 수 있게 하는 메커니즘이다.

각 객체의 컬러포인터는 GC 사이클에서 Good 또는 Bad 상태를 나타낸다. Good 상태는 해당 참조값을 바로 사용할 수 있음이며, Bad 는 객체가 이동했을 수 있으니 체크해야한다(potentially invalid) 는 뜻이다.

참조값의 Load 시 미리 정의된 Load Barrier 트랩이 발동하여 Good 이라면 참조값을 반환하고, Bad 라면

컬러 포인터 비트들

  • marked0, marked1 : 이번 마킹 사이클에서 이미 live로 ‘표시(mark)’되었다고 알려져 있는가?
    • 왜 2비트인지는 위 Cycle 설명 참조
  • remapped : 리로케이션 셋(relocation set) 안(옛 위치)을 가리키지 않는다고 알려져 있는가
    • 1 : 재배치 집합을 가리키지 않는다는게 확실하다 = 재배치된 객체 주소
    • 0 : 재배치 집합을 가리킬 수 있다 = 재배치 필요 확인 후 재배치 또는 참조 갱신

Good Color, Bad Color 판단

goodMaskbadMask
marked0001110
marked1010101
remapped100011

GC Cycle 에서는 위 그림과 같이 현재 시점의 Good Color 가 정해진다. Mutator Thread 의 참조 또는 GC Thread 의 Marking 이 실행되면, 참조값의 Color pointer 와 현재 시점의 Good Color 를 비교하여 다르다면, Bad 상태로 간주하고 처리를 한다. 이 동작은 Barrier(LB/MB) 로서 동작한다.

Color Pointer 를 확인하고 참조를 최신화하는 이 동작은 3군데서 발생한다.

  1. Marking/Remapping Phase 에서 Marking 시 Marking Barrier
  2. Marking/Remapping Phase 에서 Mutator 에 의한 조회 Load Barrier
  3. Relocation Phase 에서 Mutator 에 의한 조회 Load Barrier

각 단계에서 실제로 어떤 단계로 어떻게 처리하는지는 위의 Cycle 설명에서의 스켈레톤 코드를 참조하자.

self healing

위 Cycle 에서 설명했듯, ZGC 는 Relocation 직후에 이동한 참조값에 대해 STW 를 하여 갱신하지 않는다.

유저 스레드 동작과 함께 천천히 진행한다.

이는 객체 조회시 Relocation 이전 참조를 가지고 있다 하더라도, Color Pointer 를 통해 이전 참조임을 판단하고 Fowarding Table 을 통해 새 참조값을 가져오는 동작을 객체를 참조하는 Mutator Thread 가 직접 할 수 있기 때문이다.

이를 Self-Healing 이라 한다.

Multi Mapping


https://dev.java/learn/jvm/tool/garbage-collection/zgc-deepdive/

위 그림은 아마 객체 주소에 42bits 를 사용하던 시절의 그림이라 4, 8, 10 이 된것 같다. 44bits 를 사용한다면 1, 2, 4 가 되어야 할 듯.

Color Pointer 는 객체 참조 주소값 자체에 저장된다. 주소를 나타내는 값을 임의로 변경했다. ZGC 가 관리하는 메모리 공간에서는 이를 알고 있으나, OS 는 다르다. Color Pointer 가 다르면 다른 주소로 인식 할 뿐이다.

하지만 컬러포인터가 다르더라도 이들은 모두 같은 객체다. 따라서 자바의 가상 주소 공간에서 Color Pointer 로 다른 주소 공간을 나타내더라도 OS 메모리 공간에는 하나의 메모리 주소를 표현하도록 Multi-Mapping 을 사용한다.

컬러 포인터의 장점

  • 한 리전안의 객체들이 이동하면 그 즉시 해당 리전을 재사용할 수 있다. Self Healing 덕분이다. 섀넌도어처럼 이전 객체를 유지 할 필요가 없다.

    • Cycle 에서 STW3 에 빈 Page(마킹이 하나도 없는 Page) 를 최대한 빨리 회수하고, ReLocation 과정에서도 Page 단위로 Relocation 한 뒤 바로바로(최대한 빨리) Page 를 회수한다.
  • Read Barrier(LB/MB) 만 가지고도 처리할 수 있으므로 장벽의 수가 많이 줄어든다.

  • 아직 자바 객체 주소에는 사용하지 않는 Bit 가 많이 남아서, 확장 가능성이 열려 있다.

https://openjdk.org/jeps/333

컬러 포인터의 제한

  • 32bit 시스템은 ZGC 를 못쓴다.
  • Compressed OOPS 를 사용하지 못한다.
  • ZGC 주소공간 메모리가 16TB(2^44) 로 제한된다.

세대 구분 저지연 컬렉터

세대 구분 이론은 세가지 가설로 탄생했다. 그 중 대다수 객체는 일찍 죽는다는 가설로 Young GC 를 자주 실행시킨다. 이는 곧 죽을 확률이 높은 객체만을 GC 판별한다는 것이다. 이는 GC 의 CPU 사용 효율을 높일 수 있다.

ZGC 나 섀넌도어는 처음에는 세대 구분 없이 이를 출시했다. 세번째 가설에 의한 세대간 참조를 해결하는 기억 집합이 필요 없었기에, 메모리 사용량은 약간 줄 수 있었다.

하지만 결국 매 GC 마다 (세대 구분 GC 였다면 Old 였을) 오래 살아남는 객체를 매번 판별해야했다. 이것이 결국 ZGC, 섀넌도어도 세대 구분으로 발전하게된 이유다.

세대 구분 ZGC 는 높은 CPU 효율로 단일 세대 ZGC 보다 높은 처리량을 달성할 수 있다.
또한, 일시 정지 시간이 줄어들기에 P99 Latency 를 완화할 수 있다.

단일 세대 GC 의 가장 큰 문제는 할당 지연(allocation stall) 이다. 이는 GC 속도가 객체 할당속도보다 느릴 때 발생한다. 세대 구분 ZGC 는 같은 일시 정지 시간에 더 많은 객체를 정리 할 수 있으므로 이 또한 많이 완화되었다.

https://inside.java/2023/11/28/gen-zgc-explainer/

저지연 가비지 컬렉터의 선택

저지연 가비지 컬렉터는 회수조차 유저 스레드와 동시에 실행하기 때문에 극단적인 일시정지 시간 감소를 가져올 수 있었다.

일시 정지 시간이 길면 어떤게 나쁠까? P99 의 유저 경험이 나빠질 수 있다는 것이다.
한번씩 발생하는 긴 일시정지는 유저에게 한번씩 큰 지연 시간을 겪게 할 것이다.
반면 유저 스레드와 동시에 실행하는 것들은 많아지기 때문에 전체적인 유저의 지연 시간은 좀 더 늘어날 수 있다. CPU 자원을 나눠 쓰며 컨텍스트 스위칭 등이 일어날 수 있기 때문이다.

정리하자면 이렇다.
긴 정지 시간을 줄일 수록 P99 의 Latency 는 완화할 수 있으나 전체적인 Latency 는 조금 늘어날 수 있으나.
긴 정지 시간을 허용하면 그 시간동안 오롯이 GC 를 실행하여 전체적인 처리량 및 Latency 는 줄어든다. 하지만 긴 정지 시간에 요청을 보낸 유저는 긴 시간 후 응답을 받을 것이다.

따라서 유저 대상 서비스와 연결된 자바 애플리케이션이라면 정지 시간을 줄이는 저지연 컬렉터가 좋다.
반대로 유저를 대상으로 하지 않고 뒷단에서만 동작하는 배치, 데이터 정제 등의 작업 들은 처리량 컬렉터가 좋을 것이다.

이론상은 위와 같은데, 현재 많은 연구결과들은 처리량이든 지연시간이든 ZGC 가 좋다~ 라는 것 같다. 이는 참고만 하고 각자 환경에서 테스트하는게 제일 중요할 것.

ZGC 기대 가능한 장점

  • P99 응답 지연의 감소

기대 가능한 단점

참고

섀넌도어

[논문] Shenandoah - Christine H. Flood...
https://tech-notes.accel.dk/programming/java/java12/docs/PPPJ2016.pdf?utm_source=chatgpt.com

shipliev 이사람 자료들이 좀 자세하고 좋음..
https://shipilev.net/talks/javazone-Sep2018-shenandoah.pdf

openjdk
https://wiki.openjdk.org/spaces/shenandoah/pages/25002018/Main?utm_source=chatgpt.com

섀넌도어 제작한 redhat 자료.
https://developers.redhat.com/blog/2019/06/27/shenandoah-gc-in-jdk-13-part-1-load-reference-barriers?p=602377
https://developers.redhat.com/blog/2019/06/28/shenandoah-gc-in-jdk-13-part-2-eliminating-the-forward-pointer-word?utm_source=chatgpt.com
https://developers.redhat.com/blog/2019/07/01/shenandoah-gc-in-jdk-13-part-3-architectures-and-operating-systems?p=606497#
https://developers.redhat.com/articles/2021/05/20/shenandoah-garbage-collection-openjdk-16-concurrent-reference-processing?utm_source=chatgpt.com#traditional_reference_processing
https://developers.redhat.com/articles/2024/05/28/beginners-guide-shenandoah-garbage-collector?utm_source=chatgpt.com#

jep
404 - generational Shenandoah

ZGC

[논문] Deep Dive into ZGC: A Modern Garbage Collector in OpenJDK - Albert Mingkun Yang..
기본 개념 설명 여기서 보는게 제일 좋음
https://dl.acm.org/doi/full/10.1145/3538532

Naver D2 개념 설명. 2등. 같이 보기.
https://d2.naver.com/helloworld/0128759#ch1-3

https://www.blog-dreamus.com/post/zgc%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C

https://dev.java/learn/jvm/tool/garbage-collection/zgc-deepdive/

openjdk
https://wiki.openjdk.org/spaces/zgc/pages/34668579/Main

ppt 그림자료 좋음
https://www.jfokus.se/jfokus18/preso/ZGC--Low-Latency-GC-for-OpenJDK.pdf
https://cr.openjdk.org/~pliden/slides/ZGC-PLMeetup-2019.pdf

jep
333 - A Scalable Low-Latency Garbage Colloector
439 - Generational ZGC
490 - Remove the Non-Generational Mode

세대구분 ZGC
https://inside.java/2023/11/28/gen-zgc-explainer/

ZGC Lower Tail Latencies(P99)
https://www.morling.dev/blog/lower-java-tail-latencies-with-zgc/

Netflix 세대구분 ZGC 전환
https://netflixtechblog.com/bending-pause-times-to-your-will-with-generational-zgc-256629c9386b

AWS EMR HBase 에서 G1 vs ZGC
https://aws.amazon.com/ko/blogs/big-data/improve-amazon-emr-hbase-availability-and-tail-latency-using-generational-zgc/

parallel vs g1 vs zgc : throughput, latency..
https://kstefanj.github.io/2021/11/24/gc-progress-8-17.html

profile
안녕하세요!

0개의 댓글