G1GC Garbage Collector에 대해 알아보기 - 2

#GC#G1GC#Garbage-Collector

2023-04-10 11:14

대표 이미지

전편에 이어서, G1GC의 목적에 따른 옵션 가이드를 제공합니다. 프로젝트에 최적화 된 GC를 적용해 보세요.

개요


G1GC 의 목적과 옵션별 역할에 대해서 예제와 함께 설명한다.

Details: Option Example


export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC"
export JAVA_OPTS="$JAVA_OPTS -XX:MaxGCPauseMillis=100"
export JAVA_OPTS="$JAVA_OPTS -XX:InitiatingHeapOccupancyPercent=10"
export JAVA_OPTS="$JAVA_OPTS -XX:ParallelGCThreads=2"
export JAVA_OPTS="$JAVA_OPTS -XX:ConcGCThreads=2"
 
 
-Xms4096m -Xmx4096m -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:InitiatingHeapOccupancyPercent=10 -XX:+UseLargePagesInMetaspace -XX:ParallelGCThreads=8 -XX:ConcGCThreads=8 -Dfile.encoding=UTF-8 -verbose:gc -ea

InitiatingHeapOccupancyPercent

  • 통칭 IHOP 이라고 부른다.
  • 최초 마킹 발동 기준이 되는 값으로써 Old Generation 사이즈에 대한 백분율이다. 즉, 전체 크기 대비 Heap의 사용 비율이 해당 임계값을 넘어가면 GC가 동작한다.
  • default 값은 45. (전체 크기 대비 heap 사용이 45%가 넘어가면 Marking 시작)
  • 이 옵션이 낮을수록 Mixed Collection 이 자주 일어나며 Heap 에 대한 GC 와 Compaction 빈도가 많아지게 된다.
  • G1GC 가 Old 영역 Compact 및 클린징을 위한 Mixed Collection을 트리거 하는데, 그 기준값이 되는 옵션중에 하나다.

Mixed GC

Full GC를 완료하는 시점에 Young/Old Region을 동시에 GC 한다.

Adaptive IHOP

  • 수집한 통계 데이터(마킹 소요 시간 및 주기)를 기반으로 최적의 IHOP 값을 찾아내 자동으로 설정해준다.
  • -XX:-G1UseAdaptiveIHOP 으로 on/off 한다.
  • dafault 값은 true.
  • -XX:InitiatingHeapOccupancyPercent 옵션을 초기값으로 사용한다. (Adaptive IHOP off 시 해당 값을 계속 사용한다.)

Note:

G1 has both concurrent (runs along with application threads, e.g., refinement, marking, cleanup) and parallel (multi-threaded, e.g., stop the world) phases. Full garbage collections are still single threaded, but if tuned properly your applications should avoid full GCs.

ParallelGCThreads

  • STW에 동작하는 스레드
  • 사용 가능한 프로세서 수가 8보다 작은 경우 지정한 값을 사용하고, 그 외에는 5/8만큼의 스레드를 추가로 사용한다.
    • 예) 사용가능한 프로세스 수가 13개 → 8(기본)+(13-8)*(5/8)=11.125 → 11개 사용
  • 단, 최대 heap 사이즈에 의해 제한을 받는다.

ConGCThreads

  • 애플리케이션이 여러 페이즈와 함께 동작할때 사용하는 스레드.
  • -XX:ParallelGCThreads를 4로 나눈 값이다.

Number of threads concurrent garbage collectors will use. The default value varies with the platform on which the JVM is running.

MaxGCPauseMillis

  • 최대 일시 정지 목표 시간.
  • default는 200ms.
  • 이는 반드시 보장하는 값은 아니며, Full GC 시에는 해당되지 않는다.

UseLargePagesInMetaspace

  • Jdk8 이후부터 Perm영역이 아니라 MetaSpace에 클래스 정보를 올리는데, 이 영역이 크면 GC 소요시간이 크다. 이때 이 옵션을 사용해야 언로딩에 의한 Pause 타임을 줄일수 있다.

G1ReservePercent

  • 여유 공간을 유지하기 위한 메모리 공간에 대한 백분율이다.
  • default는 10.
  • GC 이후 Old 영역 승급시 할당된 공간이 없으면 실패가 나는 경우를 줄이기 위해 미리 heap 공간을 확보한다.
  • 실패로 인해 발생하는 일련의 이벤트의 시간 비용이 매우 크기 때문에 이를 방지하기 위한 옵션.

G1HeapRegionSize

  • Region 사이즈 비율, 가급적 기본값을 사용하는 것을 권장.
  • default는 최대 heap 사이즈의 1/2048.
  • 설정 가능 사이즈는 1 ~ 32MB 정도이며, 반드시 2의 거듭제곱 값이어야 한다.

G1HeapWastePercent

  • 얼마나 많은 region이 낭비되어도 괜찮은지 결정한다. 즉, Heap이 낭비해도 좋다고 판단하는 값이다.
  • Mixed cycle의 종료 시점을 결정한다.
  • default는 10.
  • Mixed Collection 이후 Old Region의 Reclaim 후보군이 해당 임계점보다 낮을경우 Skip하게 된다.

G1MixedGCCountTarget

  • 1회 Mixed GC 때 처리할 Region의 개수.
  • default는 8.
  • 값이 작을수록 Mixed GC가 여러 주기에 거쳐 가비지를 수거한다. 회당 일시 중지 시간은 증가하나 전체로 봤을 때는 빠르게 Region을 비울 수 있음.
  • Mixed Collection은 G1HeapWastePercent 임계값보다 낮추기 위한 사이클로 수행하는데, 이는 예측 가능한 Pause Time 에 맞게 수행하기 위함이다.

G1MixedGCLiveThresholdPercent

  • Old Region 내 라이브 객체의 점유율이 해당 옵션보다 높다면 GC 대상에서 제외된다. (Space-Reclamation 단계에서 수집되지 않는다.)
  • 다시 말해 Old Region에 임계값보다 많은 오브젝트들이 사용중이면 Old 영역 Reclaim 이 일어나지 않는다.
  • default 값은 85.
  • 이 상황에서 Survior to Old 로 프로모션이 계속 일어나는 경우 할당 공간이 더이상 없어 Full GC가 발생한다.

Details: Option 튜닝


JDK 8부터 PermGen 영역이 사라지고 Metaspace영역으로 대체되었다. 크기를 제한하던 이전과 달리 OS native 메모리를 사용 할 수 있다.

G1GC의 목표는 높은 처리량에 비해 비교적 적고 균일한 일시 중지 시간(STW)를 제공하는 것이다.

높은 처리량

  • -XX:MaxGcPauseMillis(최대 일시 정지 시간) 증가 혹은 힙 크기 확장을 통해 일시 중지 시간을 길게 만들어 준다.

지연 시간 최소화

  • -XX:MaxGcPauseMillis(최대 일시 정지 시간) 감소

단, Young Generation은 설정된 일시 중지 시간을 충족 할 수 있도록 만드는 주요 수단이므로, -Xmn, -XX:NewRatio 등의 옵션을 사용해 Young Generation의 크기를 특정 값으로 강제하지 않도록 한다.

Details: 성능 향상


기본적으로 G1은 추가 옵션 지정없이도 전반적으로 우수한 성능을 제공할 수 있도록 설계되었다. 하지만 기본 휴리스틱(hueuristics, 추론)이나 구성이 차선의 결과를 제공하는 경우가 있으므로, 이를 진단하고 개선할 수 있는 몇 가지 방법을 가이드 해주고 있다.

Full GC

old generation의 높은 힙 점유율로 인한 Full GC는 로그에서 Pause Full(Allocation Failure)로 표시된다.

일반적으로 to-space exhausted 태그로 표시되는 evacuation 실패가 선행된다.

Full GC가 발생하는 이유는 일반적으로 다음과 같다.

  1. 애플리케이션이 신속하게 회수 가능한 수 이상으로 객체를 너무 많이 할당한 경우 → Concurrent marking을 마치지 못하고 급하게 space-reclamation phase를 시작하게 됨
  2. G1 할당 방식에 의해 다수의 큰 객체가 할당된 경우 → 예상보다 더 큰 메모리를 차지하게 됨으로써 발생

Concurrent marking이 목표 시간에 종료 될 수 있도록 조정해 줌으로써 개선이 가능하다.

  1. old generation의 할당 비율을 줄인다.
  2. Concurrent marking을 완료할 수 있을 때까지 충분한 시간을 제공한다.

다음 옵션들을 적용해 봄으로써 개선한다.

  1. Heap region 사이즈 조정
    • -XX:G1HeapRegionSize 증가: 객체 크기가 커서 humongous region에 들어갈 수 밖에 없던 객체를 young region에 들어 갈 수 있게 만든다.
    • 극단적인 경우, 객체 할당에 필요한 연속 공간이 불충분 할 수 있다. Full GC로 연속 region을 회수 할 수 없는 경우 VM이 종료되어버리는 문제가 발생한다. 이런 경우 humongous 객체 할당량을 감소시키거나 heap size를 늘리는 방법 뿐이다.
  2. Java heap 사이즈 조정
    • -XX:G1HeapRegionSize를 명시하지 않는 경우. region size는 전체 heap size에 의존하므로 1과 동일한 효과를 가짐
    • 단, 마킹 시 사용되는 시간이 증가하는 문제가 생길 수 있다.
  3. Concurrent marking에 사용되는 thread 수 조정
    • -XX:ConcGCThreads 증가
  4. 마킹하는 시점을 당김(GC가 더 자주 일어나도록 한다.)
    • -XX:G1ReservePercent 증가: (Adaptive IHOP 계산에 사용되는 버퍼 증가) space-reclamation을 시작할 시점에 대한 목표치를 낮춘다.
    • -XX:-G1UseAdaptiveIHOP=false-XX:InitiatingHeapOccupancyPercent 설정: Adaptive IHOP 계산을 비활성화 시킨다.
  5. 외부 문제에 의한 유발 최소화
    • -XX:+ExplicitGCInvokesConcurrent 설정 : Full GC의 영향을 감소시킨다.
    • -XX:+DisableExplicitGC 설정

지연시간 조정

image.png 로그에서는 일시 중지 시간 동안 어떻게 시간을 보냈는지 알 수 있다.

  • User Time : VM 코드에서 보낸 시간
  • System Time : OS에서 보낸 시간
  • Real Time : 일시 중지 중 경과된 절대(absolute) 시간

다음 상황별 해결책들을 적용해 보자.

  1. System Time(OS)에서 보낸 시간이 상대적으로 높은 경우, 대부분 환경이 원인이다.
    • VM이 OS 메모리로부터 메모리를 할당하거나 반환하는 경우
      • -Xms, -Xmx: 최소/최대 힙 크기를 동일하게 설정한다.
      • -XX:+AlwaysPreTouch: 해당 작업을 VM시작단계로 이동시킨다. 즉, 미리 메모리를 할당/반환시킨다.
    • Linux의 THP(Transparent Huge Pages)기능에 의해 작은 Page를 큰 Page로 병합하다가 임의의 프로세스가 중단되는 경우
      • OS 설정을 통해 해당 기능을 비활성화 할 필요가 있을 수 있다.
    • 로그가 기록되는 하드 디스크의 모든 I/O 대역폭을 간헐적으로 차지하는 일부 백그라운드 작업에 의해 로그 쓰기가 중단되는 경우.
      • 로그 작성을 위한 별도의 디스크를 사용하는 것을 추천한다.
  2. Real > User + System 인 경우 → VM 과부하 가능성이 있는 시스템에서 충분한 CPU 시간을 얻지 못했음
  3. Reference Object 처리 시간이 너무 오래 걸리는 경우
    • Reference Object는 -XX:ParallelGCThreads 값을 기준으로하여 싱글 스레드로 작동한다.
    • -XX:ReferencesPerThread=0 or -XX:-ParallelRefProcEnabled(병렬화 비활성화): 사용가능한 모든 스레드를 사용하도록 설정한다.
  4. Young-Only 단계에서 Young-Only Collection 시간이 오래걸리는 경우
    • Young Collection은 CSet 내 라이브 객체 수에 비례하는 시간이 소요된다.
    • CSet의 Evacuate 단계, 특히 sub-phase인 객체 복사 시간이 지연되는 경우
      • -XX:G1NewSizePercent감소: Young generation의 최소 크기를 줄인다.
    • 애플리케이션의 성능, 특히 컬렉션에 남아 있는 객체의 양이 급변하는 경우
      • -XX:G1MaxNewSizePercent감소: Young generation의 최대 크기를 줄여준다. → 일시 중지 중 처리해야하는 개체 수를 제한
  5. Mixed Collection 시간이 오래걸리는 경우
    • Young Generation에서의 시간이 긴 경우 (4)를 참조한다. 여기서는 Old Generation 처리가 느린 경우만 다룬다.
    • -XX:G1MixedGCCountTarget 증가: Old generation 내 Region 처리 개수를 늘린다.
    • -XX:G1MixedGCLiveThresholdPercent 감소: GC 대상을 감소시킨다.
    • -XX:G1HeapWastePercent 증가: 높은 메모리를 차지하는 Region은 수집하지 않도록 한다.
  6. RSet 업데이트 및 스캔 시간이 긴 경우
    • Region 내용이 이동될 때(Evacuation) Rset이 업데이트된다. 단, 즉시 업데이트 되지 않고 효율을 위해 일괄 처리된다. 이 때 애플리케이션에 따라 상당한 시간이 소요될 수 있다.
    • -XX:G1HeapRegionSize: heap region 크기 조정
      • cross-region references 수와 RSet의 크기에 영향을 준다.
      • region이 클 경우, cross-region references가 적은 경향이 있으므로 상대적으로 처리에 소요되는 작업량이 감소된다. 단, 각 region이 더 많은 라이브 객체를 가지므로 다른 단계의 지연 시간이 증가할 가능성이 있다.
    • -XX:G1RSetUpdatingPauseTimePercent: RSet update에 사용되는 시간을 강제화
      • 해당 옵션값이 작을 수록 G1은 더 많은 RSet 업데이트 작업을 동시에 수행시킨다.
    • -XX:G1RSetRegionEntries 증가: RSet이 압축되는 양을 감소 시킴으로써 값을 검색하는데에 걸리는 시간을 줄인다.

처리량 조정

높은 처리량을 원하는 경우

  • -XX:MaxGCPauseMillis 증가: 최대 일시 중지 시간 늘림 (빈도는 감소)
  • -XX:G1MaxNewSizePercent 증가 : young generation 최대 크기 증가 → 처리량 증가
  • 동시 작업을 위한 RSet 업데이트에 리소스 소모가 크므로 동시 작업량 감소시켜 처리량 증가시킨다.
    • -XX:G1RSetUpdatingPauseTimePercent 증가 : 동시 작업 감소, 일시 중지 시간 증가
    • -XX:-G1UseAdaptiveConcRefinement, -XX:G1ConcRefinementGreenZone=2G, -XX:G1ConcRefinementThreads=0: 극단적인 방법으로써 RSet 업데이트 비활성화 후 다음 GC로 이월시킬 수 있다.
  • heap 사이즈 조정 작업을 끄거나 최소화한다.
    • -Xms, -Xmx를 같은 값으로 설정
    • -XX:+AlwaysPreTouch를 설정

결론


이번 포스팅은 G1GC Option Guide를 통해 실제 G1GC를 세밀하게 컨트롤하는 방법을 알아보았다. 옵션 조정을 통해 어떤 상황에서든 GC에 있어서는 최상의 성능을 내는 애플리케이션을 설계해 보자.

Ref