About ZGC

 Z Garbage Collector

JDK 11 에서 실험적인 기능으로 소개했다. JDK 15 에서 정식으로 출시 되었다. 물론 JDK 21에서도 사용할 수 있다.
ZGC 어플리케이션 스레드 실행을 10ms 이상 중단하지 않고 고비용 작업을 동시에 수행한다. 지연 시간은 힙 사이즈와 무관하며, 몇백 MB의 작은 힙부터 16테라의 매우 큰 힙을 사용해도 잘 작동하낟.

JDK 11 에서 JDK 15 이하 버전에서는  -XX:+UnlockExperimentalVMOptions 와 -XX:+UseZGC 옵션을 동시에 사용하여 활성화한다. JDK 15 이상 버전에서는 -XX:+UseZGC 하나의 옵션으로 사용할 수 있다.

아래의 핵심 기능을 사용하며 ZGC 또한 G1 GC 와 마찬가지로 concurrent 가비지 컬렉터이다.

  • Concurrent
  • Region-based
  • Compacting
  • NUMA-aware
  • Using colored pointers
  • Using load barriers
  • Using store barriers (in the generational mode)

Configuration & Tuning

ZGC 도 G1 GC 와 유사하게 최소한의 설정을 필요로 하며, 어플리케이션이 실행하는 과정에서 스스로 적응하는 기능이 있다. ZGC 은 동적으로 세대를 리사이징하고, GC 스레드수도 조절하며, tenuring thresholds 도 조절한다. 주요 튜닝 포인트는 최대 힙 사이즈를 늘리는 것이다. (-Xmx)
ZGC 는 generational version 과 non-generational version 이 있는데 non-generational version 은 레거시이며, 실행중에 세대 개념을 사용하지 않는다. JDK 21부터 최신 버전인 generational version 이 출시 되었으며 최신 버전 사용을 권장한다.
generational version 은 -XX:+UseZGC 옵션과 -XX:+ZGenerational 옵션을 사용한다.
non-generational version 은 -XX:+UseZGC 옵션 하나로 사용한다.

Setting Heap Size

Headroom

JVM Heap Headroom 은 힙 메모리에서 실제 사용 중인 데이터를 저장하기 위해 할당된 메모리 양과 JVM 힙의 최대 크기(-Xmx) 사이에 남아 있는 여유 공간을 의미한다. 이 여유 공간은 시스템이 더 많은 객체를 생성하거나 데이터를 처리할 수 있게 해주며, GC 의 지연시간을 줄이는데 도움이 된다.

헤드룸이 없으면 OOM 이 발생에 따른 장애로 이어질 수 있고, 너무 많은 여유 공간을 두는 것은 시스템 자원의 낭비가 될 수 있다.  JVM Heap Headroom 을 모니터링하고 관리하는 것은 성능 최적화와 안정성 유지에 필수적인 부분이다. 효율적으로 실행될 수 있도록 하고 메모리 관련 문제를 예방할 수 있다.

ZGC의 가장 중요한 튜닝 옵션은 -Xmx 옵션으로 최대 힙사이즈를 설정하는 것이다. ZGC 는 concurrent collector 이기 때문에,  GC 가 동작하는 동안에 서비스가 동작할 수 있게 메모리 할당이 가능하도록 충분한 헤드룸이 힙에 있고 어플리케이션의 live-set 을 수용할 수 있어야 하는 힙 사이즈를 선택해야만 한다.  헤드룸의 크기는 어플리케이션의 할당률과 live-set 의 크기에 의존한다. 일반적으로, ZGC 에 힙 메모리를 많이 줄수록 좋지만, 너무 과도한 힙 사이즈를 잡는것은 바람직하지 않기에 균형을 잘 잡는 것이 중요하다.

ZGC 에서 힙사이즈와 관련된 다른 옵션으로 -XX:SoftMaxHeapSize 가 있다. Heap 이 얼마나 크기까지 커질수 있는지에 대해서 soft limit 을 설정하는데 사용된다. ZGC 는 이 한도를 넘지 않도록 노력하지만, 최대 힙 사이즈까지 한도를 넘을 수도 있다. 어플리케이션이 멈춰서 GC가 메모리를 회수할 때까지 기다리는 것을 방지하기 위해 필요한 경우에만 Soft limit 을 초과하여 사용한다. 예를 들어, -Xmx5g -XX:SoftMaxHeapSize=4g 로 설정했을 때, ZGC 는 일반적으로 4GB 까지만 사용하지만 일시적으로 5GB 까지 사용할 수 있다.



Setting Concurrent GC Threads

non-generational version 을 사용하는 경우 concurrent GC threads 를 -XX:ConcGCThreads=<number> 로 설정하여 튜닝한다. ZGC 는 자동으로 스레드 수를 설정하는 휴리스틱이 있다. 이 휴리스틱은 대개 잘 동작하지만 어플리케이션의 특성에 따라 조절이 필요하다. 이 옵션은 근본적으로 GC 에 얼마나 CPU 시간을 할당할지 결정한다. 너무 많은 수를 설정하면 GC 는 과도한 CPU 시간을 어플리케이션으로 부터 뺏고 어플리케이션의 성능 저하로 이어질 수 있다. 너무 적게 수를 설정하면 어플리케이션이 GC 가 수집하는 것보다 빠르게 garbage 를 할당 할 수 있다.

JDK 17 부터의 ZGC 는 concurrent GC threads 수를 동적으로 확장했다가 축소했다가 하기 때문에, 옵션을 통해서 설정할 필요가 없어진다.

JDK 21 부터는 generational version 이 출시되고 concurrent GC threads 수를 설정할 일이 더욱 없어졌다.

어플리케이션이 GC 지연을 최소화하고 빠른 응답이 필요하다면 CPU 사용률이 70% 이상 넘지 않도록 해야한다.

Returning Unused Memory to the Operating System 

기본 설정으로, ZGC 사용하지 않는 메모리를 커밋 해제하여 OS 에게 반환한다. OS 에게 미사용 메모리를 반환하는 것은 어플리케이션 스레드가 동작하는데 지연 시간에 부정적인 영향을 줄 수 있다. 이 기능을 -XX:-ZUncommit 옵션을 통해서 비활성화 할 수 있다. 더욱이, 최소 힙사이즈(-Xms) 아래로 축소되지 않도록 커밋 해제를 하지 않게 된다. -Xmx 와 -Xmx 를 동일하게 설정하면 암시적으로 이 기본 설정을 비활성화 한다.

커밋 해제 딜레이를 -XX:ZUncommitDelay=<seconds> (기본 300초) 옵션을 통해서 설정할 수 있다. 딜레이는 얼마 동안 메모리가 사용되지 않아야 커밋 해제의 대상이 되는지는 나타낸다. 기본으로 300초 즉 5분 동안 메모리가 사용되지 않으면 커밋 해제하고 OS 에게 반환한다.

어플리케이션이 실행 중일 때 GC 가 메모리를 커밋하거나 커밋 해제하는 것은 어플리케이션 스레드의 지연 시간에 부정적인 영향을 줄 수 있다. 따라서 빠른 응답 속도를 원한다면, -Xmx 와 -Xms 를 같게 설정해야 한다. 그리고 메모리를 페이지로 이동하기 위해서 JDK 14 부터 지원하는 -XX:+AlwaysPreTouch 옵션을 사용해야 한다. 하지만 JVM 시작될 때 할당된 모든 힙 메모리 페이지를 물리 메모리에 미리 매핑하도록 강제하기 때문에, cloud 환경에서 컨테이너 기반으로 사용할 때 컨테이너에 설정한 메모리보다 더 많은 메모리를 사용할 수 있기 때문에 문제가 발생할 수 있다.

리눅스에서, 미사용 메모리 커밋 해제를 위해서는 커널 3.5(for tmpfs) 와 4.3(for hugetlbfs) 에서 처음 나온 FALLOC_FL_PUNCH_HOLE 을 지원하는 fallocate(2) 함수가 필요하다. 

Using Large Pages

large pages 를 사용하도록 설정하면 일반적으로 처리량, 지연 시간, 시작 시간 등 좋은 성능을 보인다. 하지만 설정하는데 복잡하다. 이런 설정에서 루트 권한을 필요로 한다. 따라서 기본적으로는 비활성화 되어 있다. 해당 링크에 설정 방법이 있다. https://wiki.openjdk.org/display/zgc/Main#Main-JDK17

Enabling NUMA(Non-Uniform memory access) Support

기본으로 해당 기능은 활성화 되지만, JVM 이 단일 NUMA 노드에서만 메모리를 사용할 수 있는 것을 감지한다면 자동으로 비활성화 될 수 있다. 대개 이 설정은 신경 쓰지 않아도 된다. 하지만 명시적으로 JVM 의 결정을 덮어쓰고 싶다면 -XX:+UseNUMA 나 -XX:-UseNUMA 로 수동으로 끄고 킬 수 있다. NUMA 머신(e.g a multi-socket x86 machine) 에서 실행할 때, 해당 옵션이 활성화 되어 있으면 눈에 보이는 성능 향상을 확인할 수 있다.  


참고

댓글

이 블로그의 인기 게시물

About JVM Warm up

About idempotent

About Kafka Basic

sneak peek jitpack

Spring Boot Actuator readiness, liveness probes on k8s

About Websocket minimize data size and data transfer cost on cloud

About G1 GC

대학생 코딩 과제 대행 java, python, oracle 네 번째