Kotlin & Java

JVM Garbage Collection Algorithm

상쾌한기분 2025. 4. 10. 13:59
반응형

 

JVM Garbage Collection Algorithm

Serial GC

  • 가장 단순한 형태의 GC 알고리즘으로 단일 스레드로 동작.
  • 단일 스레드에서 동작을하여 쓰레드간 통신 오버헤드가 발생하지 않아 경우에 따라서 상대적으로 효율적일 수도 있다.
  • 싱글 코어에서 100MB 미만의 데이터를 다루는 경우 이점을 가질 수 있다.
  • 특정 하드웨어나 OS 등의 환경에서는 default로 사용된다. (조선시대 환경)
java -XX:+UseSerialGC -jar Application.java

Parallel GC

  • Java8 Default GC
  • Throughput Collector 로도 불리며 Serial GC와 유사하다. 다른 점은 멀티 쓰레드 환경에서 GC를 수행한다는 것이다.
  • 멀티 코어 환경에서 중간-큰 사이즈를 처리하는 어플리케션을 위해 설계
  • Minor GC 와 Major GC 모두 멀티 쓰레드로 수행
    병렬 압축(Parallel Compactiion) 특징으로 Major GC에서도 병렬로 수행할 수 있고 병렬 압축이 아니라면 싱글 쓰레드에서 동작한다. (UseParallelOldGC 옵션으로 제어할 수 있다.)
java -XX:+UseParallelGC -XX:ParallelGCThreads=<n> -jar Application.java

Garbage-First (G1) GC

  • Java9 Default GC
  • 멀티 코어 환경에 대용량 메모리 환경을 타겟으로 설계
  • 힙 메모리 구조를 Region 개념을 사용
  • 짧고 예측 가능한 GC Pause Time과 높은 처리량(Throughput)을 특징으로 다음 환경의 어플리케이션에서 최적화 되어 있다.
    • 수십 GB 이상의 힙 사이즈 (50% 이상이 살아있는 데이터)
    • 객체 할당과 승격(Promote)의 비율이 계속 다양하게 변경
      • 신규 객체 생성과 참조 되어 살아남은 객체(Survivor, Old 영역으로 이동)의 비율
    • 수백 ms 안으로 Pause Time이 예측 가능
    • 힙 조각화(Fragmentation) 대응
      • Region 기반 구조로 힙 조각화를 완하하고 메모리 압축 가능
  • 다른 GC에 비교하여 짧은 중단 시간을 갖지만 처치량은  조금 낮은 경향이 있다.
  • 가장 많은 Garbage가 존재하는 Region을 우선 수집(Grabage First)
java -XX:+UseG1GC -jar Application.java

Region 기반 힙 구조

전체 힙을 동일한 크기의 Region으로 나누며 (기본값: 1MB ~ 32MB) 각각의 Region은 동적으로 아래 중 하나가 될 수 있다.

  • Young
    • Eden: 빨간색
    • Survivor: 빨간색 + S
  • Old: 파랑색
  • Humongous: 파란색 + H
    • Region보다 큰 객체 전용
  • Free

GC 동작 방식

G1 GC는 크게 두 단계로 반복 수행 된다.

  1. 젊은 세대 전용 단계(Young-Only phase)
    1. 젊은 세대에서 수집된 객체들이 늙은 세대로 이동되면서 늙은 세대의 사용량이 증가된다.
  2. 공간 회수 단계(Space-Relcamation phase)
    1. 젊은 세대와 늙은 세대에서 사용되지 않는 객체를 제거하고 메모리 공간을 확보한다.

Option and Default value Description
-XX:MaxGCPauseMillis=200 최대 중단 시간 
-XX:GCPauseTimeInterval= 최대 중단 시간의 간격, 극단적인 경우 연속적으로 GC가 수행될 수 있다.
기본값: 미설정
-XX:ParallelGCThreads=  병렬 작업에 사용되는 최대 쓰레드 수.
기본값: 현재 CPU의 쓰레드 수가 8 이하라면 해당 값으로 설정,
8 초과라면 (초과 쓰레드 수) * 5 / 8 + 8 값으로 설정
-XX:ConcGCThreads=  동시 작업에 사용되는 최대 쓰레드 수.
기본값: ParallelGCThreads / 4
-XX:+G1UseAdaptiveIHOP 힙 점유율 제어.
기본값: 힙 점유율을 자동으로 결정하며, 초기 컬렉션 주기 동안 구세대 메모리의 45%를 마크 시작 임계값으로 사용
-XX:InitiatingHeapOccupancyPercent=45
-XX:G1HeapRegionSize=  힙 Region 크기.
기본값: 최대 힙 크기에 기준으로 계산되며 약 2048 Region을 생성되도록 계산된다. 
반드시 2의 제곱 값이여야 하며 크기는 (1MB ~ 32MB) 이여야 한다.
-XX:G1NewSizePercent=5 젊은 세대(young generation)의 전체 크기는 현재 사용 중인 Java 힙(heap) 메모리의 일정 비율로 결정되며, 이 값은 주어진 두 값 사이에서 변동 된다.
-XX:G1MaxNewSizePercent=60
-XX:G1HeapWastePercent=5 이 비율보다 컬렉션 후보 세트의 여유 공간이 적으면 공간 회수 단계(Space-Reclamation phase) 를 중단
-XX:G1MixedGCCountTarget=8 공간 회수 단계(Space-Reclamation phase)의 예상 소요 시간을 컬렉션(collection) 횟수로 표기한 값
-XX:G1MixedGCLiveThresholdPercent=85 이 비율보다 사용 중인 객체(live object) 비중이 높은 올드 세대(Old generation) 영역은 현재 공간 회수 단계(Space-Reclamation phase)에서 수집되지 않는다.

 

 1. 올드 세대(Old Generation) 처리 방식

  • Parallel GC: 올드 세대 전체를 한 번에 압축(compact)하고 회수합니다. → 긴 정지 시간(pause time) 발생
  • G1: 여러 번의 짧은 컬렉션으로 작업을 분산시켜 처리합니다. → 정지 시간 단축 (대신 처리량(throughput) 약간 감소 가능)

 2. 동시성(Concurrency) 차이

  • G1은 올드 세대 공간 회수(spacereclamation) 작업의 일부를 백그라운드에서 동시에 수행할 수 있습니다.
  • 반면, Parallel GC 등은 STW(Stop-The-World) 방식으로만 동작합니다.

 3. 오버헤드 및 처리량(Throughput)

  • G1은 동시성 작업으로 인해 다른 수집기보다 오버헤드가 높아 처리량이 낮을 수 있습니다.
  • ZGC (대용량 힙 전용)는 G1보다 더 짧은 정지 시간을 목표로 하지만, 처리량은 더욱 감소합니다.

 G1의 고유한 최적화 기능

 빈 대형 객체(Humongous Objects) 즉시 회수

  • G1은 완전히 비어 있는 대형 올드 세대 영역을 즉시 회수할 수 있습니다.
    → 불필요한 GC 발생 감소 + 메모리 효율성 향상
    (비활성화: -XX:-G1EagerReclaimHumongousObjects)

 문자열 중복 제거(String Deduplication)

  • Java 힙 내 중복 문자열을 동시에 검사해 제거하여 메모리 사용량을 줄입니다.
    (기본 비활성화, 활성화: -XX:+G1EnableStringDeduplication)

The Z GC

ZGC는 확장성(Scalable) 있는 저지연(Low-Latency) GC로 애플리케이션을 최대 몇 ms의 중단 시간을 가지며 모든 고비용 작업을 동시(Concurrent)로 처리 한다.

  • 초저지연으로 애플리케이션 중지 시간은 몇 ms 이내며 힙 크기와 무관하다. (대용량에서도 일관된 성능)
  • 광범위한 힙 크기 지원으로 8MB ~ 16TB 까지 동작한다.
  • 동시성(Concurrent) 중심 설계로 메모리 압축(Compatction), 마킹(Marking) 등 부한 큰 작업을 백그라운드에서 처리한다.
java -XX:+UseZGC -jar Application.java

힙 사이즈  (-Xmx)

ZGC의 가장 중요한 튜닝 옵션은 최대 힙 크기 지정이며 다음 두 조건을 만족하도록 해야한다.

  1. 애플리케이션의 사용 중인 객체 메모리 사용량(Live-set)를 수용할 수 있을 것
  2. GC가 실행 중일 때에도 메모리 할당이 가능하도록 충분한 여유 공간(Headroom)을 확보할 것
    1. 필요한 여유공간 크기는 애플리케이션의 할당 속도(Application Rate)와 사용 중인 객체 메모리 사용량 크기에 좌우된다.
    2. 일반적으로 ZGC 메모리를 많이 할당할수록 성능이 향상되지만, 불필요한 메모리 낭비 방지를 위해 적절한 균형을 찾아야 한다.
  • 라이브 세트(Live-set): 현재 어플리케이션에서 실제로 사용 중인 객체들의 총 메모리 사용량
  • 여유 공간(Headroom): 새 객체 할당을 위한 추가 메모리 버퍼
  • 할당 속도(Allocation rate): 애플리케이션이 초당 객체를 생성하는 속도

동시성 GC 스레드 수 (-XX:ConcGCThreads)

두번째로 중요한 튜닝 옵션은 동시성 GC 스레드 수 지정이다. ZGC는 자동으로 이 값이 자동으로 선택되며 대부분 변경이 필요 하지 않지만 애플리케이션 특징에 따라 변경이 필요할 수 도 있다.

  • 값을 너무 크게 설정하면 애플리케이션이 사용할 CPU가 부족
  • 값을 너무 작게 설정하면 GC가 수집하는 속도보다 garbage가 더 빨리 쌓임.

메모리 반환(Uncommit) 동작

  • 사용하지 않는 메모리는 OS로 자동 반환하며 메모리 사용량을 줄이는 환경에 유용하다.
    • 비활성화 -XX:-ZUncommit
  • 힙 크기는 최소 값 아래로 줄어들지 않는다.
    • -Xms 설정
    • 비활성 -Xms=1G -Xmx=1G 로 동일하게 설정시
  • 메모리 반환 지연 시간 설정
    • 기본값은 300초로 -XX:ZUncommitDelay=<second>
    • 지정된 시간동안 미사용 상태라면 OS에 반환

출처

https://docs.oracle.com/en/java/javase/17/gctuning/hotspot-virtual-machine-garbage-collection-tuning-guide.pdf

 

728x90
반응형