JAVA GC - Parallel GC

  1. Parallel GC란?
    1. 처리량(Throughput) 중심으로 동작하는 JVM GC
    2. Parallel GC의 기본 개념
    3. UseParallelOldGC 는 왜 같이 언급될까?
    4. Parallel GC는 왜 Throughput 에 강할까?
    5. Parallel GC의 GC Threads는 어떻게 결정될까?
    6. 머신 스펙에 따라 Parallel GC 성능은 어떻게 달라질까?
      1. 1. Single-core 환경
      2. 2. Dual-core 환경
      3. 3. Multi-core 환경
    7. Parallel GC는 어떤 애플리케이션에 잘 맞을까?
    8. 정리 해보자
    9. Parallel GC의 PLAB
      1. PLAB가 왜 필요할까?
      2. TLAB와 비슷하지만 대상이 다릅니다.
      3. Parallel GC에서 PLAB는 어디에 사용될까?
      4. 객체를 복사할 때 어떤 방식이 가능할까?
        1. to-survivor로 복사하는 경우
        2. old generation 으로 promotion 되는 경우
    10. Parallel Collector Ergonomics
      1. 목표 우선순위 요약
      2. Parallel GC가 우선적으로 맞추려는 3가지 목표
      3. 목표의 우선순위는 어떻게 될까?
      4. 왜 generation size를 자동으로 조정할까?
      5. Parallel GC - Generation Size 재조정
        1. Generation Size 재조정 관련 주요 옵션
      6. 축소 비율은 어떻게 계산될까?
        1. 왜 확장과 축소 비율을 다르게 둘까?
    11. Parallel GC - Generation Size를 줄일 때와 늘릴 때
      1. Generation size를 감소시킬 때
        1. 그럼 어떻게 줄일까?
        2. Young generation만 줄이는 경우
        3. 둘 다 목표를 못 맞췄을 때
      2. Generation size를 증가시킬 때
        1. 증가 비율은 어떻게 결정될까?
        2. young : old GC time = 1 : 3
        3. young : old GC time = 3 : 1

Parallel GC란?

처리량(Throughput) 중심으로 동작하는 JVM GC

JVM의 여러 Garbage Collector 중에서 Parallel GC는 대표적인 처리량(Throughput) 중심 collector 입니다. 즉, GC로 인한 애플리케이션 중단 시간을 아주 짧게 만드는 것보다는, 전체적으로 애플리케이션이 더 많은 일을 처리하도록 돕는 것에 초점이 맞춰져 있습니다. 그래서 Parallel GC는 흔히 Throughput GC 라고도 불립니다.

특히 사용자 응답 속도보다 전체 작업량이 더 중요한 환경, 예를 들어 배치 작업이나 대량 데이터 처리 같은 워크로드에서 잘 어울립니다.

Parallel GC의 기본 개념

Parallel GC는 구조적으로는 Serial GC와 비슷하지만, 큰 차이점이 하나 있습니다. 바로 GC 작업을 여러 개의 GC 스레드로 병렬 수행 한다는 점입니다.

즉, Minor GC(Young GC) 도 병렬로 수행하고 Major GC(Full GC) 도 병렬로 수행할 수 있습니다. 이 때문에 단일 GC 스레드로 동작하는 Serial GC 보다 CPU 코어 수가 충분한 환경에서는 더 높은 처리량을 기대할 수 있습니다.

Parallel GC를 사용하려면 다음 옵션을 명시하면 됩니다.

1
-XX:+UseParallelGC

JDK 8까지는 server-class machine 에서 기본 GC로 사용되던 시기도 있었습니다.

UseParallelOldGC 는 왜 같이 언급될까?

Parallel GC를 설명할 때 자주 같이 나오는 옵션이 있습니다.

1
-XX:+UseParallelOldGC

초기에는 -XX:+UseParallelGC 를 사용하더라도 Young Generation 수집만 병렬로 수행하고 Old Generation 수집은 Serial 방식으로 처리 하던 시기가 있었습니다. 그래서 Old Generation 까지 병렬로 수집하려면
별도로 -XX:+UseParallelOldGC 옵션을 함께 지정해야 했습니다.

하지만 이후 JDK 7u4 부터는 -XX:+UseParallelGC 만 지정해도-XX:+UseParallelOldGC 가 함께 활성화되도록 바뀌었습니다. 즉, 현대적인 관점에서는 Parallel GC를 사용할 때 Old 영역 병렬 수집까지 하나의 동작으로 이해해도 큰 무리는 없습니다.

다만 이 옵션은 이후 버전에서 deprecated, obsoleted, expired 과정을 거쳤기 때문에 JDK 버전에 따라 의미가 다를 수 있다는 점은 같이 적어두면 좋습니다.

Parallel GC는 왜 Throughput 에 강할까?

Parallel GC의 핵심은 간단합니다.

GC 작업을 하나의 스레드가 처리하지 않고, 여러 GC 스레드가 나눠서 처리한다.

예를 들어 힙이 크고 정리해야 할 객체도 많다면 GC 작업을 여러 스레드가 동시에 수행하는 편이 전체 수집 시간을 줄이는 데 유리할 수 있습니다.

이 방식은 특히 다음과 같은 상황에서 효과적입니다.

  • 힙이 비교적 큰 경우
  • 객체 생성과 정리가 많이 발생하는 경우
  • 멀티코어 CPU를 충분히 활용할 수 있는 경우

즉 Parallel GC는 CPU 자원을 더 적극적으로 써서 GC 처리량을 높이는 collector라고 볼 수 있습니다.

Parallel GC의 GC Threads는 어떻게 결정될까?

Parallel GC는 이름 그대로 여러 GC 스레드를 사용합니다. 그렇다면 이 스레드 수는 어떻게 정해질까요?

기본적으로는 Ergonomics 가 시스템 환경을 보고 자동으로 결정합니다. 즉, JVM이 머신의 CPU 코어 수 또는 하드웨어 스레드 수를 기준으로 GC 스레드 수를 잡습니다.

예를 들어:

  • 물리 코어가 6개
  • 하이퍼스레딩으로 12개의 h/w thread 제공

이라면 JVM은 보통 12를 기준으로 판단할 수 있습니다. 또한 컨테이너 환경에서는 호스트 전체 CPU가 아니라 컨테이너에 할당된 CPU 수를 기준으로 동작할 수 있습니다.

GC 스레드 수를 계산하는 대표적인 방식은 다음과 같습니다.

  • N <= 8 이면 GC threads = N
  • N > 8 이면 GC threads = 8 + (N - 8) * 5 / 8

일부 시스템에서는 조금 다른 계산식이 적용될 수 있습니다. 그리고 필요하다면 다음 옵션으로 직접 지정할 수도 있습니다.

1
-XX:ParallelGCThreads=M

이 옵션을 사용하면 JVM의 자동 판단 대신 GC 스레드 수를 명시적으로 고정할 수 있습니다.

특히 한 대의 장비에서 여러 Java 프로세스를 함께 실행하는 경우에는 GC 스레드 수를 직접 조정해서 CPU 경쟁을 줄이는 것이 도움이 될 수 있습니다.

머신 스펙에 따라 Parallel GC 성능은 어떻게 달라질까?

Parallel GC는 멀티스레드 기반 collector 이기 때문에 CPU 환경에 따라 효과가 꽤 다르게 나타납니다.

1. Single-core 환경

단일 코어 환경에서는 Parallel GC의 장점이 거의 드러나지 않을 수 있습니다. 오히려 GC 스레드 간 동기화 오버헤드 때문에 Serial GC보다 불리할 수도 있습니다.

즉, 병렬 처리를 위한 구조는 갖고 있지만 실제로 병렬로 돌릴 CPU 자원이 부족하면 이점이 줄어듭니다.

2. Dual-core 환경

듀얼 코어 정도의 환경에서는 중간 크기 이상 힙을 사용하는 애플리케이션에서 Serial GC보다 조금 더 나은 성능을 보일 수 있습니다.

아주 큰 차이는 아닐 수 있지만, GC 작업을 나눠 처리할 수 있는 최소한의 여건이 갖춰지기 시작하는 구간이라고 볼 수 있습니다.

3. Multi-core 환경

멀티코어 환경에서는 Parallel GC의 장점이 훨씬 뚜렷해집니다. GC 작업을 여러 스레드가 동시에 수행할 수 있기 때문에, Serial GC보다 훨씬 더 높은 처리량을 기대할 수 있습니다.

즉, Parallel GC는 본질적으로 코어 수가 충분한 환경에서 힘을 발휘하는 collector 입니다.

Parallel GC는 어떤 애플리케이션에 잘 맞을까?

Parallel GC는 Latency(짧은 멈춤 시간) 보다는Throughput(전체 처리량)을 더 중요하게 보는 환경에 적합합니다.

예를 들면 다음과 같습니다.

  • 배치 프로그램
  • 로그/통계 집계 작업
  • 대량 데이터 처리
  • 사용자 응답 속도보다 전체 작업 완료 시간이 더 중요한 시스템

반대로, 사용자 요청마다 응답 시간이 매우 민감한 시스템에서는 Parallel GC의 긴 Stop-The-World pause가 부담이 될 수 있습니다.

즉, Parallel GC는 좋은 GC라기보다 어떤 목표를 우선하느냐에 따라 적합성이 갈리는 GC 라고 보는 것이 더 정확합니다.

정리 해보자

Parallel GC는 JVM의 대표적인 처리량 중심 GC입니다. Young 영역뿐 아니라 Old 영역까지 병렬로 수집할 수 있으며, 여러 GC 스레드를 활용해 전체 작업 효율을 높이는 데 강점이 있습니다.

핵심만 정리하면 이렇습니다.

  • Parallel GC는 Throughput 중심 collector다
  • -XX:+UseParallelGC 로 사용할 수 있다
  • 여러 GC 스레드로 Minor GC / Major GC를 병렬 수행한다
  • CPU 코어가 많을수록 장점이 커진다
  • 배치, 데이터 처리처럼 전체 처리량이 중요한 환경에 잘 맞는다
  • 짧은 pause time이 중요한 시스템에서는 다른 collector가 더 적합할 수 있다

결국 Parallel GC는 “GC pause를 얼마나 짧게 만들까?” 보다“전체적으로 얼마나 많은 일을 처리할까?” 를 우선하는 collector라고 이해하면 됩니다.

Parallel GC의 PLAB

Parallel GC의 PLAB 란 GC 스레드 간 할당 충돌을 줄이기 위한 버퍼 입니다.

Parallel GC를 보면 애플리케이션 스레드용 버퍼인 TLAB 외에도 GC 자체가 사용하는 PLAB(Promotion Local Allocation Buffer) 이라는 개념이 나옵니다.

이름만 보면 조금 낯설 수 있지만, 핵심은 단순합니다. Parallel GC의 각 GC 스레드survivor 또는 old generation 으로 객체를 복사할 때, 서로 같은 메모리 영역을 두고 경쟁하지 않도록 쓰는 전용 버퍼 즉, PLAB는 GC 과정에서 발생할 수 있는 GC thread 간 동기화 병목을 줄이기 위한 장치라고 보면 됩니다.

PLAB가 왜 필요할까?

Parallel GC는 이름 그대로 여러 개의 GC 스레드가 동시에 동작합니다. Young GC가 발생하면 각 GC 스레드는 자신이 맡은 GC root부터 시작해서 살아 있는 객체를 추적하고, 살아 있는 객체를 다른 영역으로 복사합니다.

이때 문제가 생길 수 있습니다. 여러 GC 스레드가 동시에 survivor 영역에 객체를 복사하거나 old generation으로 promotion 하려고 하면 결국 같은 목적지 메모리 공간에 동시에 객체를 배치하려는 상황이 발생할 수 있습니다.

이걸 매번 전역 동기화로 해결할 수도 있지만 그렇게 되면 Parallel GC의 장점인 병렬성이 크게 줄어듭니다. 그래서 Parallel GC는 각 GC 스레드에게 자기만 사용하는 작은 복사 버퍼, 즉 PLAB 를 줍니다.

TLAB와 비슷하지만 대상이 다릅니다.

PLAB는 개념적으로 TLAB와 꽤 비슷합니다.

  • TLAB: 애플리케이션 스레드가 Eden에 객체를 빠르게 할당하기 위한 버퍼
  • PLAB: GC 스레드가 survivor / old 영역에 객체를 빠르게 복사하기 위한 버퍼

즉, 둘 다 “경합을 줄이기 위한 thread-local 성격의 버퍼” 라는 점에서는 비슷합니다. 하지만 차이점은 분명합니다.

  • TLAB는 application thread 에게 할당됨
  • PLAB는 GC thread 에게 할당됨

이 차이를 한 문장으로 정리하면 이렇습니다.

TLAB는 객체 생성 시점의 병목을 줄이기 위한 버퍼이고, PLAB는 GC 중 객체 복사 시점의 병목을 줄이기 위한 버퍼다.

Parallel GC에서 PLAB는 어디에 사용될까?

Parallel GC의 각 GC 스레드는 보통 다음 두 경우에 PLAB를 사용합니다.

to-survivor 복사
Young GC 중 살아남은 객체가 survivor 영역으로 이동할 때 사용됩니다. 즉, Eden 또는 from-survivor에 있던 살아 있는 객체가 to-survivor로 복사될 때 GC 스레드는 자기 PLAB를 활용할 수 있습니다.

old generation promotion

객체의 age가 다 찼거나, survivor에 더 이상 공간이 부족한 경우 해당 객체는 old generation으로 promotion 됩니다. 이때도 각 GC 스레드는 old generation 쪽 PLAB를 활용해 promotion 대상을 복사할 수 있습니다.

즉, PLAB는 단순히 promotion 전용 이라기보다, GC 중 복사(copy)가 필요한 대상 영역에서 GC 스레드가 충돌 없이 빠르게 할당할 수 있게 해주는 버퍼라고 이해하면 됩니다.

객체를 복사할 때 어떤 방식이 가능할까?

GC 스레드가 객체를 survivor 또는 old generation으로 복사할 때는 대체로 다음과 같은 흐름으로 생각할 수 있습니다.

to-survivor로 복사하는 경우

살아 있는 객체를 to-survivor로 옮길 때는 다음 중 하나로 처리될 수 있습니다.

  • 현재 GC thread의 PLAB 안에 바로 할당
  • direct 할당
  • PLAB 공간이 부족하면 refill 후 할당

즉, 가장 이상적인 경우는 자기 PLAB 안에 바로 넣는 것이고, 그게 안 되면 다른 경로를 사용하게 됩니다.

old generation 으로 promotion 되는 경우

객체가 old generation 으로 가야 하는 상황은 보통 다음과 같습니다. 객체 age가 임계치에 도달한 경우 survivor 공간이 부족한 경우 이때도 처리 방식은 비슷합니다.

  • old용 PLAB 안에 할당
  • direct 할당
  • 공간이 부족하면 refill 후 할당

즉 survivor든 old든 핵심은 같습니다. 가능하면 각 GC 스레드가 자기 PLAB 안에서 처리하고 안 되면 direct allocation 또는 refill 과정을 거치게 됩니다.

Parallel-GC_PLAB

위 그림은 Parallel GC에서 PLAB가 어떻게 쓰이는지를 이해하는 데 도움이 됩니다.

전체 힙 안에 young generationold generation이 존재 합니다. 각 GC thread는 survivor / old 쪽에 자기만의 PLAB 를 가질 수 있습니다.

Young GC 중 객체가 이동할 때 각 GC thread는 자기 PLAB를 우선 사용 되고, 이렇게 하면 여러 GC 스레드가 같은 survivor 또는 old 영역에 동시에 직접 할당하려고 하면서 생기는 경쟁을 줄일 수 있습니다.

Parallel Collector Ergonomics

Parallel GC는 단순히 Young/Old 크기를 고정해서 사용하는 방식이 아니라, 행동(behavior) 기반 목표를 기준으로 JVM이 heap size와 generation size를 자동 조정하는 collector입니다.

Ergonomics는 보통 아래 3가지 목표를 우선순위에 따라 고려합니다.

목표 우선순위 요약

우선순위 목표 의미 관련 옵션 기본값
1 Maximum pause-time goal GC로 인한 최대 STW 시간을 가능한 목표 이하로 맞추려는 것 -XX:MaxGCPauseMillis=N 제한 없음
2 Throughput goal 전체 실행 시간 중 GC 시간이 너무 커지지 않도록 조정 -XX:GCTimeRatio=N 99
3 Minimum footprint goal 다른 목표를 만족하는 범위 안에서 heap을 가능한 작게 유지 -XmxN 물리 메모리(또는 컨테이너 메모리)의 대략 1/4 수준에서 시작

Parallel GC가 우선적으로 맞추려는 3가지 목표

Parallel Collector ergonomics는 대표적으로 다음 3가지 목표를 다룹니다.

  1. Maximum GC pause time

가장 먼저 보는 것은 최대 pause time 목표입니다. 이 목표는 -XX:MaxGCPauseMillis=N 으로 지정합니다. 중요한 점은 이 값이 “반드시 지켜지는 절대 보장”이 아니라, 그 이하로 맞추고 싶다는 힌트(hint) 라는 점입니다.

즉, -XX:MaxGCPauseMillis=200 을 줬다고 해서 항상 모든 STW 가 200ms 이하가 된다고 단정하면 안 됩니다. Parallel GC는 그 목표에 맞추기 위해 힙 크기와 다른 파라미터를 조정해보지만, 애플리케이션 특성상 못 맞출 수도 있습니다.

  1. Throughput

두 번째 목표는 Throughput 입니다. 이 목표는 -XX:GCTimeRatio=N 으로 지정하며, GC 시간과 application time의 비율을 1 / (1 + N) 으로 해석합니다. 예를 들어 -XX:GCTimeRatio=19 면 전체 시간 중 GC 시간 목표가 5%라는 뜻입니다. Parallel Collector 문서에서는 기본값이 99 이고, 이는 GC 시간 1%, application time 99% 목표에 해당한다고 설명합니다.

즉, Parallel GC는 throughput 관점에서 “GC에 너무 많은 시간을 쓰지 말고 애플리케이션이 일하는 시간을 최대한 확보하자” 라는 방향으로 동작합니다. 그래서 Parallel GC가 흔히 Throughput Collector 로 불리는 이유도 여기와 연결됩니다.

  1. Footprint

세 번째는 Footprint 즉 힙 크기 자체를 가능한 작게 유지하려는 목표입니다.

쉽게 말하면 pause time 목표도 만족하고 throughput 목표도 만족한다면 그 다음에는 굳이 힙을 크게 유지하지 말고 줄일 수 있는지 를 본다는 뜻입니다.

목표의 우선순위는 어떻게 될까?

Parallel Collector ergonomics에서 중요한 포인트 하나는 이 세 목표를 동시에 같은 비중으로 맞추는 것이 아니라 우선순위를 가지고 본다는 점입니다.

우선순위는 다음과 같습니다.

  1. Maximum pause-time goal
  2. Throughput goal
  3. Minimum footprint goal

즉, 먼저 pause 목표를 맞추려 하고, 그 다음에 throughput을 보고, 마지막으로 footprint를 줄이려 합니다.

이 순서를 이해하면 왜 heap size가 오르락내리락하는지도 설명이 됩니다. 어떤 시점에는 throughput을 맞추기 위해 힙을 키우고, 다른 시점에는 footprint를 줄이기 위해 힙을 줄이려 하기 때문입니다.

왜 generation size를 자동으로 조정할까?

Parallel GC는 각 GC가 끝날 때마다 평균 pause time 같은 통계값을 업데이트 하고, 그 결과 목표를 만족하지 못하면 generation 크기를 조정합니다. 예를 들어 pause 목표를 못 맞추면 세대 크기를 줄이는 쪽으로, throughput 목표를 못 맞추면 세대 크기를 키우는 쪽으로 움직일 수 있습니다.

Parallel GC - Generation Size 재조정

Parallel Collector는 매 collection 이 끝날 때마다 평균 pause time 같은 통계를 갱신한다. 그 뒤 현재 목표들이 달성되었는지 검사하고 필요하면 young generation과 old generation의 크기를 다시 조정 한다. 단 System.gc() 같은 명시적 GC는 이런 통계 갱신과 세대 크기 조정 판단에서 제외 됩니다.

또한 Parallel Collector는 generation 크기를 한 번에 크게 바꾸지 않고, 현재 크기의 일정 비율만큼 단계적으로 증가/감소시키는 방식으로 desired size에 가까워지도록 조정 합니다.


Generation Size 재조정 관련 주요 옵션

항목 설명 VM 옵션 디폴트 값
young generation increment (%) young generation이 확장할 때 한 번에 몇 % 증가할지 결정 -XX:YoungGenerationSizeIncrement=N 20%
old generation increment (%) old generation이 확장할 때 한 번에 몇 % 증가할지 결정 -XX:TenuredGenerationSizeIncrement=N 20%
decrement scale factor generation 확장 비율 대비 축소 비율을 얼마로 할지 결정하는 scale factor -XX:AdaptiveSizeDecrementScaleFactor=N 4
young generation decrement (%) young generation이 축소될 때 한 번에 몇 % 감소할지 결정 위 옵션들로 계산 5%
old generation decrement (%) old generation이 축소될 때 한 번에 몇 % 감소할지 결정 위 옵션들로 계산 5%

축소 비율은 어떻게 계산될까?

Parallel Collector는 확장(grow)축소(shrink) 를 같은 비율로 하지 않는다. 기본적으로 generation은 20%씩 확장하고, 5%씩 축소 합니다.

축소 비율은 아래 식으로 이해할 수 있습니다.

1
decrement(%) = increment(%) / AdaptiveSizeDecrementScaleFactor

기본값을 넣으면 다음과 같다.

20 / 4 = 5(%)

즉,

  • Young 확장: 20%
  • Old 확장: 20%
  • Young 축소: 5%
  • Old 축소: 5%

로 이해하면 됩니다.

왜 확장과 축소 비율을 다르게 둘까?

Parallel Collector는 목표에 맞지 않으면 generation을 더 키워볼 수 있고, 반대로 heap이 너무 크다고 판단되면 줄이기도 한다. 그런데 확장과 축소를 같은 폭으로 반복하면 힙 크기가 너무 급하게 흔들릴 수 있다. 그래서 기본적으로는 확장은 비교적 빠르게, 축소는 더 완만하게 진행되도록 설계되어 있다.

즉, Parallel Collector는 다음처럼 동작한다고 이해하면 됩니다.

  • 목표 미달 시: generation을 상대적으로 빠르게 키움
  • 여유가 생긴 경우: generation을 천천히 줄임

Parallel GC - Generation Size를 줄일 때와 늘릴 때

Parallel GC의 Ergonomics는 매 collection이 끝날 때마다 pause time, throughput 같은 통계를 갱신한 뒤 현재 목표를 만족하고 있는지 확인하고 필요하면 young generationold generation 의 크기를 다시 조정 합니다.

한마리로 정리 하자면

  • pause-time goal을 못 맞추면 generation size를 줄이는 방향
  • throughput goal을 못 맞추면 generation size를 늘리는 방향

즉, Parallel GC는 상황에 따라 generation 크기를 자동으로 줄이거나 늘리면서 pause time과 throughput 사이의 균형점을 찾으려고 합니다.


상황 실패한 목표 Ergonomics 동작
GC pause time이 너무 길다 Maximum pause-time goal generation size를 줄인다
GC에 소비되는 시간이 너무 많다 Throughput goal generation size를 늘린다

Generation size를 감소시킬 때

Maximum pause-time goal 을 달성하지 못하면 Ergonomics가 generation size를 줄입니다.

즉, 사용자가 다음과 같이 pause time 목표를 줬을떄

1
-XX:MaxGCPauseMillis=200

실제 GC pause time이 이 목표를 자주 넘는다면 JVM은 pause time을 줄이기 위해 generation size를 재조정하려고 합니다.

그럼 어떻게 줄일까?

generation size를 줄일 때는 다음 특징이 있습니다.

  • young과 old를 동시에 줄이지 않는다.
  • 한 번에 하나의 generation만 줄인다.
  • young과 old 둘 다 pause-time goal을 달성하지 못했다면,
  • pause time이 더 길었던 generation부터 먼저 줄인다.

Parallel GC는 throughput collector이기 때문에, generation size를 줄일 때는 꽤 신중하게 움직입니다. 한 번에 둘 다 줄여버리면 GC 빈도가 급격히 증가해서 전체 처리량이 크게 흔들릴 수 있기 때문 입니다.

Young generation만 줄이는 경우

다음 상황을 가정 해보도록 하겠습니다.

  • 목표 pause time: 200ms
  • Young GC pause time: 280ms
  • Full GC pause time: 130ms

이 경우 문제는 주로 Young GC 쪽에 있습니다. Old generation 쪽은 아직 목표를 크게 넘지 않았기 때문에, Ergonomics는 먼저 young generation을 줄이는 방향으로 움직일 가능성이 높다.

즉, 이런 흐름으로 이해할 수 있습니다.

  • MaxGCPauseMillis=200 목표 확인
  • Young GC가 280ms로 목표 초과
  • Old GC는 130ms로 상대적으로 양호
  • Young generation size 축소
둘 다 목표를 못 맞췄을 때

이번에는 다음 상황을 가정 해보도록 하겠습니다.

  • 목표 pause time: 200ms
  • Young GC pause time: 260ms
  • Full GC pause time: 420ms

이 경우 young과 old 둘 다 목표를 만족하지 못했다. 하지만 pause가 더 긴 쪽은 Old generation 입니다. 이때는 JVM은 먼저 old generation 쪽을 줄이는 방향으로 조정 합니다. 즉, 둘 다 문제가 있어도 아무거나 줄이는 것이 아니라 더 긴 pause를 만든 generation부터 줄인다고 이해하면 됩니다.

Generation size를 증가시킬 때

Throughput goal 을 달성하지 못하면 Ergonomicsgeneration size를 늘립니다. Parallel GC에서 throughput goal은 보통 다음 옵션과 연결해서 이해할 수 있습니다.

generation size를 늘릴 때는 감소시킬 때와 다르게 동작 합니다. young generationold generation을 둘 다 동시에 늘립니다. 다만, 무조건 같은 비율로 늘리는 것은 아니고 각 generation을 collection하는 데 사용된 시간 비율에 따라 최종 증가 폭이 달라집니다.

증가 비율은 어떻게 결정될까?

기본 증가 폭은 아래 옵션으로 정합니다.

1
2
-XX:YoungGenerationSizeIncrement=20
-XX:TenuredGenerationSizeIncrement=20

이 값은 기본 증가 후보 폭이라고 생각하면 됩니다. 하지만 실제로는 young generation time : old generation time 비율에 따라 각 generation에 적용되는 최종 증가율이 달라집니다.

young : old GC time = 1 : 3

다음과 같은 상황을 가정 합니다.

  • throughput goal 미달
  • Young generation GC time : Old generation GC time = 1 : 3
  • -XX:YoungGenerationSizeIncrement=20
  • -XX:TenuredGenerationSizeIncrement=20

전체 GC time을 4라고 보면

  • Young generation 비중 = 1/4 = 25%
  • Old generation 비중 = 3/4 = 75%

따라서 개념적으로는 다음처럼 이해할 수 있습니다.

Young generation 증가율 = 20% × 25% = 5%
Old generation 증가율 = 20% × 75% = 15%

즉, GC 시간에 더 많이 기여한 쪽인 old generation이 더 크게 증가한다.

young : old GC time = 3 : 1
  • throughput goal 미달
  • Young generation GC time : Old generation GC time = 3 : 1
  • 증가 옵션은 동일하게 20%

이 경우 전체 비중은:

  • Young generation 비중 = 75%
  • Old generation 비중 = 25%

따라서 개념적으로는 다음처럼 볼 수 있다.

  • Young generation 증가율 ≈ 15%
  • Old generation 증가율 ≈ 5%

즉, 이번에는 young generation이 더 큰 비중으로 늘어난다. 결국 JVM은 “어디에서 GC 시간이 더 많이 소비되고 있는가?”를 보고 그에 맞게 generation size 증가 폭을 분배한다고 이해하면 됩니다.



Copyright 201- syh8088. 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.

버튼

×

喜欢就点赞,疼爱就打赏