본문 바로가기

Java/JVM

[JVM] GC 벤치마크 결과

벤치마크 결과

이번 장에서는 GC 벤치마크 결과를 살펴본다.

이번 장에서는 분석에 필요한 데이터를 나열해두고 분석해본다.
[[JVM] GC 벤치마크 결과 (링크)]에서는 이번 장에서 다루지 않은 데이터도 따로 정리해두었다.
이번 장의 각 결과 하단에는 공식문서 및 많은 블로그를 참고하여 필자의 생각을 정리해보았다.

GC 선정 결과
필자가 선정한 GC는 환경을 불문하고 Parallel GC이다.
Serial GC, ZGC, CMS는 성능이 좋다하여도 사용하기 힘든 부분이 있다.
(Serial GC는 고성능 서버자원을 모두 사용하지 않으며 ZGC는 필자가 개발한 서비스의 Java 버전과 맞지 않으며 CMS 는 Java 9에서 deprecated 되었기 때문이다. 실제로 결과들을 살펴보면 CMS가 가장 좋은 성적을 보여주었지만 제외하였다.)
현실적으로 후보는 G1GC, Parallel GC 정도라고 볼 수 있다.

필자가 Parallel GC를 선택한 이유는 간단하다. 가장 뛰어나지는 않았지만 이렇다할 단점이 없었다.
또한 Non Concurrent Collector의 가장 큰 단점인 STW가 길다라는 단점 또한 라이벌인 G1GC와 큰 차이가 없었으며 심지어 최대 STW시간은 G1GC가 높았다. 또한 G1GC의 경우 서버 자원을 상당히 많이 사용하였다. CPU 사용량 최대값의 경우 Parallel GC의 10배, Heap Peak Usage 같은 경우 4배를 사용하였다. 같은 성능이라면 당연히 자원을 적게 사용하는 쪽을 선택해야할 것이다.

(필자의 경우 당연히 G1GC가 압도적으로 좋은 성적을 보여줄 것이라고 예상하였으나 그렇지 못하였다. 필자의 경우 JVM 옵션에서 GC를 선택하는 것 이외의 옵션을 설정하지 않았는데 G1GC의 성능을 최대로 뽑아내기 위해서는 옵션을 수정해줘야할지도 모르겠다.)


Positive Ranking (순위가 높을수록 좋음)

  • Request Throughput (이하 R-TPS): Request 처리량.
    • 결과:
      모든 GC에서 동일한 0.51 R-TPS를 기록하였다.
    • 분석:
      벤치마크를 진행할 때 처리하기 힘든 수준의 Request를 요청하지 않아서 동일한 R-TPS를 기록한 것으로 예상된다.
      이러한 경우 같은 R-TPS를 기록하면서 발생한 Error Ratio가 적고 적은 자원을 사용한 GC가 더 우리 서비스에 맞는 GC로 생각할 수 있다.
  • GC Throughput (이하 G-TPS): GCEazy에서 측정한 GC 성능
    • 결과:
    순위 이름 G-TPS (throughput per second)
    1 Parallel 99.976
    2 Parallel Old 99.969
    3 CMS 99.902
    4 Serial 99.385
    5 G1GC 98.924
    예외 ZGC null

Negative Ranking (순위가 높을수록 나쁨)

  • Error Ratio: 에러 발생률
    • 결과:
    순위 이름 Error Ratio (%)
    1 CMS 5.02
    2 G1GC 4.76
    2 ZGC 4.76
    4 Parallel 4.32
    5 Serial 4.10
    6 Parallel Old 3.82
    • 분석:
      결과에 있는 Error는 대부분 DB Connection을 획득하지 못하여 발생한 Timeout Error다. 해당 Error의 경우 GC성능보다는 클라이언트로부터 요청받은 Request의 결과를 조회하기위한 Query에 의해 발생되기 때문에 압도적으로 높거나 낮은 Error Ratio를 기록한 것이 아니라면 GC의 성능을 판단하는 근거가 되기는 힘들다. (물론 GC가 수행되는 STW동안 Thread가 중단되기 때문에 완전 무관하다고 볼 수는 없다.)

 

  • Max CPU Usage: GC별로 각각 최대치 5개의 평균
    • 결과:
    순위 이름 Max CPU Usage (%)
    1 ZGC 57.52
    2 G1GC 52.1
    3 Serial 10.72
    4 CMS 9.58
    5 Parallel 6.12
    6 Parallel Old 5.26
    • 분석:
      우리는 [JVM] GC 벤치마크 분석 포인트 (링크)에서 Concurrent Collector의 경우 GC가 실행될 때 Application Thread와 GC Thread가 동시에 작업을 진행한다는 것을 확인하였다. 예상대로 ZGC와 G1GC의 경우 GC가 발생하는 동안 다른 GC와 다르게 상당히 높은 CPU 사용량을 기록하는 것을 확인할 수 있다. 하지만 CMS의 경우 Concurrent Collector이지만 Non Concurrent Collector와 비슷한 수치의 최대 CPU 사용량을 기록하였다. CMS의 작동방식을 살펴보고 위의 결과를 보면 이해하기 힘든 부분이다. 다시 한번 CMS의 벤치마킹을 진행하면서 주기적으로 Thread Dump를 기록하여 분석하면 원인 파악이 가능할 듯하다. 이 부분은 추후에 다시 살펴보도록 한다.

 

  • Average CPU Usage: 평균 CPU 사용량 (User 사용량만 계산 System 사용량은 순위 산정에서 제외)
    • 결과:
    순위 이름 Average CPU Usage (%)
    1 ZGC 6.55
    2 G1GC 5.925
    3 CMS 1.45
    4 Parallel 1.15
    4 Parallel Old 1.15
    6 Serial 1.0
    • 분석:
      Max CPU Usage와 동일.
  • Full GC & Pause GC Average Time: 어플리케이션의 평균 STW 시간을 측정, Non Concurrent Collector는 Full GC의 평균 시간, Concurrent Collector는 Pause GC의 평균 시간
    • 결과:
    순위 이름 STW Average Time (ms)
    1 Serial 677.5
    2 Parallel Old 100
    3 Parallel 92.5
    4 G1GC 58.1
    5 CMS 9.13
    예외 ZGC null
    • 분석:
      이번 결과는 어플리케이션이 실행되는 동안의 STW시간의 평균 시간이므로 상당히 중요한 부분이다.
      아직 우리 서비스의 경우 응답시간의 최대값이 결정되어 있지 않다. 하지만 우리 회사 서비스는 고객의 모든 요청을 100ms 이내에 처리해야해! 라는 규정이 정해져있다면 1 ~ 3위를 기록한 Serial, Parallel Old, Parallel GC는 사용할 수 없을 것이다. [JVM] GC 알고리즘 종류 (링크)에서 살펴본 것과 같이 Concurrent Collector의 경우 GC Thread와 Application Thread가 동시에 작업하기 때문에 GC로 인한 STW 시간이 짧은 것을 확인할 수 있다.

 

  • Full GC & Pause GC Max Time: 어플리케이션의 최대 STW 시간을 측정, Non Concurrent Collector 는 Full GC의 최대 시간, Concurrent Collector는 Pause GC의 최대 시간
    • 결과:
    순위 이름 STW Max Time (ms)
    1 Serial 2,308
    2 G1GC 375
    3 Parallel 190
    4 Parallel Old 160
    5 CMS 70
    예외 ZGC null
    • 분석:
      Full GC & Pause GC Average Time과 동일하다. 여기서 조금 더 살펴봐야할 부분은 Serial GC의 경우 Single Thread 환경에서 모든 GC를 수행하기 때문에 가장 긴 최대 STW 시간을 기록하였다. Parallel GC와 Parallel Old GC의 경우 Full GC에서 Single Thread (Parallel Old GC), Multi Thread (Parallel GC)를 사용하기 때문에 Parallel GC가 상대적으로 짧은 STW시간을 기록한 것을 알 수 있다. 다만, G1GC의 경우 최대 STW가 375 ms를 기록한 원인을 찾아보아야한다. 어플리케이션의 375 ms 중단은 G1GC의 탄생 이유를 살펴보았을 때 설명되지 않는 부분이기 때문이다.
  • Minor GC Average Time: Minor GC 평균 시간 (대상: Non Concurrent Collector)
    • 결과:
    순위 이름 Minor GC Average Time (ms)
    1 Serial 57.53
    2 Parallel Old 15.65
    3 Parallel 15.43
    • 분석:
      Serial GC의 경우 Minor GC를 Single Thread로 처리하기 때문에 당연이 높은 처리시간이 걸릴 것으로 예상할 수 있었다. Parallel GC와 Parallel Old GC의 경우 Minor GC에서는 큰 차이가 없기 때문에 비슷한 성능을 기록한 것을 확인할 수 있다.

 

  • Concurrent GC Average Time: Concurrent GC 평균 시간 (대상: Concurrent Collector)
    • 순위:
    순위 이름 Concurrent GC Average Time (ms)
    1 G1GC 5,224
    2 CMS 696
    예외 ZGC null
    • 분석:
      CMS의 경우 G1GC보다 여러 기록들에서 압도적으로 높은 성적을 보여주고 있다.
      (단순히 Compaction 단계를 진행하지 않기 때문에 이렇게 성능이 높은 것인지... 따로 CMS 특집으로 문서를 작성해보고 싶다는 생각이 든다.)
  • Process Switch Per Second (이하 PSPS): GC별로 각각 최대치 5개의 평균
    • 결과:
    순위 이름 PSPS
    1 G1GC 20,694
    2 ZGC 14,999
    3 Parallel Old 1,829
    4 CMS 1,733
    5 Serial 1,575
    6 Parallel 1,488
    • 분석:
      Concurrent Collector의 경우 Application Thread와 GC Thread가 동시에 작업을 진행하기 때문에 예상대로 높은 PSPS를 기록하였다. 하지만 Full GC에서 Single Thread를 사용하는 Parallel Old GC가 Parallel GC보다 PSPS가 더 높게 나온 부분은 확인해봐야할 부분이다. 물론 차이가 심하지 않으므로 큰 차이는 없을 것 같다. (Concurrent Collector인 CMS의 PSPS가 Non Concurrent Collector들과 비슷한 수치가 나온 것은 이해하기 힘든 부분이다. 이정도되니 CMS를 테스트할 때 무언가 잘못했던가 필자의 지식에 문제가 있음이 확실시된다...)

Neutral Ranking (GC 선택을 위한 랭킹, 좋고 나쁨의 지표가 아님)

  • Allocated Heap: OS로 부터 할당받은 Heap Memory 사이즈
    • 결과:
    순위 이름 Allocated Heap (gb)
    1 Serial 4.94
    2 Parallel 4.93
    2 Parallel Old 4.93
    4 G1GC 3.85
    5 CMS 3.84
    예외 ZGC null
    • 분석:
      JVM에서 자동으로 할당한 Heap 영역이므로 큰 의미는 없다.
      다만 낮은 사양에서 사용되는 Serial GC에 G1GC나 CMS보다 큰 Heap 영역 할당한 것은 예상치 못한 부분이다. (추후 JVM이 Heap 영역을 할당하는 기준(?)에 대해서 공부해보면 좋을 듯하다.)
  • Average Active Memory Usage (이하 AAMU): 평균 메모리 사용량.
    • 결과:
    순위 이름 AAMU (mb)
    1 G1GC 5,050
    2 ZGC 3,950
    3 Parallel Old 2,319
    4 Parallel 2,168
    5 CMS 1,407
    6 Serial 1,270
    • 분석:
      CPU 사용량과 다르게 AAMU의 경우 중립적인 부분으로 두었다. Memory를 많이 사용하는 경우(Old 영역도 Memory양과 비례하게 설정한다는 가정하에) Full GC의 발생빈도는 줄어들게 될 것이다. 이러한 경우 Application 이 중단되는 빈도는 줄어들게 되고 STW의 시간이 늘어나게 될 것이다. 만약 Memory의 사용량이 줄어들면 STW의 시간은 줄어들겠지만 Full GC의 발생빈도는 늘어나게 될 것이다. 이러한 성향은 회사에서 추구하는 좋은 서비스 품질의 관점에 따라서 좋고 나쁨이 나뉘어지게 될 것이다.

 

  • Peak Usage: 최대 메모리 사용량, AAMU와 동일
    • 결과:
    순위 이름 Peak Usage (mb)
    1 ZGC 3,990
    2 G1GC 3,040
    3 Parallel Old 1,370
    4 Parallel 1,220
    5 CMS 335
    6 Serial 206
    • 분석:
      AAMU와 같다. Serial GC의 경우 Heap 최대 사용량이 206 mb 밖에 되지 않는다. 이에 반해 Allocated Heap은 4.94 gb로 20배 이상 높다. 이는 할당받은 Memory를 적절히 사용하지 못했다고 볼 수 있다. (물론 관점에 따라서 Peak Usage가 너무 커지는 경우 Single Thread 특성상 STW 시간이 다른 GC에 비해 많이 늘어나므로 206 mb만 사용하는 선에서 멈추었다고 볼 수도 있을듯 하다.)

이것으로 GC 종류별로 벤치마크를 진행하고 필자의 서비스에 맞는 GC를 선택하는 모든 과정을 마치게 되었다.
많은 논문 및 공식문서와 블로그를 참고하여 작성하였지만 부족한 부분이 많고 틀린 부분이 있을 수도 있다.

잘못된 부분을 발견하였다면 언제든 지적해 주시면 감사하겠습니다.

마지막으로 이번에 진행하지 못한 두 가지 TODO로 남겨두고 마무리하고자 한다.

  • TODO - 1) ZGC 작동방식 정확히 이해하고 다시 한번 분석하기.
  • TODO -2 ) CMS의 경우 예상한 것과 다른 결과가 나왔는데 원인 파악하기.

'Java > JVM' 카테고리의 다른 글

[JVM] Stack & Frame  (0) 2022.02.28
[JVM] GC 벤치마크 결과 데이터  (0) 2022.01.23
[JVM] GC 벤치마크 분석 포인트  (0) 2022.01.23
[JVM] GC 벤치마크 개요  (0) 2021.12.23
[JVM] GC 알고리즘 종류  (0) 2021.12.02