티스토리 뷰

JAVA공부/JVM

GC 정리

CodingDreamTree 2024. 10. 7. 02:11

Garbage Collection (가비지 컬렉션) 이란?

Java에서 Heap메모리영역에서 자동으로 더 이상 참조(사용)되지 않는 인스턴스(메모리 영역)들을

관리 및 청소해주고 메모리 파편화를 줄여주는 JVM 프로세스의 일부분입니다.

(PS. C와 같은 일부 언어에서는 이 작업을 수동으로 관리해주어야합니다.)

 

 

어떠한 원리로 작동하는가?

작동 원리는 간단합니다.
1. 인스턴스 생성시 메모리를 할당합니다. (힙)

2. 사용되는 메모리 영역과 사용되지 않는 메모리 영역을 식별합니다.
3. 사용되지 않는 메모리를 회수하여 운영체제에 다시 전달합니다.

 

왜 기존 GC는 Young영역 과 Old 영역을 나누었을까? (Z GC 제외)

 

출처: Garbage Collector Implementation (oracle.com)

 

위는 객체 수명에 대한 분포도에 대한 그림입니다.

시간이 오래될 수록 살아있는 객체들이 짧은 시간내의  개체들에 비해 적다는 것을 알 수 있습니다.

이 수명이 짧은 객체들이 많아서 효율적으로 수집하기 위해 Young영역의 수집과 Old영역의 수집을 나누어서

Young영역에 대한 수집이 주로 이루어날 수 있도록 한 것입니다.

 

GC로 메모리 수거가 일어나지 않는다면??

병렬 스레드로 OOM를 유도했을때의 Heap 패턴

 

- 메모리: 설정에 의해  최대 메모리가 늘어나게되다가, 최종적으로 OOM이 발생하게 됩니다.

해당 스레드가 병렬스레드인 경우 해당 스레드를 강제 종료하고 메모리를 회수하게 됩니다.

병렬스레드로 메모리 회수로도 자원 확보가 안되는 경우와 메인스레드인 경우 프로그램을 강제 종료하게 됩니다.

 

OOM발생시 CPU 패턴

 

- CPU: 메모리를 확보하기 위한 GC작업이 계속 일어나게됩니다.

CPU 사용률은 성능 과 사용메모리에 따라 많은 사용률이 쓰이지 않을 수 있습니다.

위와 같이 GC를 유도하였을때, Heap 메모리를 64mb -> 128mb -> 512mb -> 1024mb 로 진행해보았는데,

크면 클 수록 GC가 발생하였을때 CPU 사용률이 큰폭으로 증가하였고, OOM발생 이후에 다시 줄어드는 것을

확인할 수 있었습니다.

 

GC와 관련된 주요한 설정들

- 일시정지시간 최대 설정: 애플리케이션이 GC로 인해 일시정지 되는 최대 시간을 설정합니다. (기본값200ms)  
(옵션: -XX:MaxGCPauseTimeMillis

 - Young영역 비율 설정: Young영역 크기를 키우면 처리량이 늘어나고, GC시간이 늘어나게됩니다.
Young크기를 작게 만들면 처리량이 줄고 GC시간이 줄어들게됩니다.
(옵션: -XX:G1NewSizePercent)

 

- 힙 크기 조정: 시작시 시간을 줄이기 위해 시작 메모리를 설정하고, 시스템 메모리를 넘어 메모리를 사용을 제한하고,
최적의 GC성능을 위해 최대 힙 크기를 조정합니다.
(옵션: -Xms -Xmx)

 

- G1GC에서 중복 문자열객체 제거 활성화: 중복 문자열 객체 체크후 동일한 참조를 바라보도록 수정(불변이기때문)
(옵션: -XX:+UseStringDeduplication -XX:+PrintStringDeduplicationStatistics)

 

- 로깅: GC 동작과정, GC 전 후 메모리 상태, 영역이동 과정등 여러가지 상황을 볼 수 있습니다.
(옵션: -XX:+PrintGCDetails)

 

-OOM 힙덤프: OOM 발생시 힙덤프를 떠서 분석할 수 있도록 도와주는 설정입니다.

(옵션: -XX:+HeapDumpOnOutOfMemoryError, XX:HeapDumpPath)

 

 

 

GC 종류와 어떻게 선택할 것 인지?

- Serial GC (직렬 가비지 컬렉터): 단일 스레드를 사용하여 가비지 수집을 수행합니다.

병렬 스레드 처리를 하지 않기 때문에 통신 오버헤드가 존재하지 않습니다. 

단일 프로세서에 가장 적합하지만, 작은 데이터 세트(최대 약 100MB) 의 다중 프로세서 애플리케이션 사용하거나

일시 중지 시간(Stop The World가 일어나는 시간) 요구사항이 없는 경우에서 유용할 수 있습니다.

 

- Paralle GC (병렬 가비지 컬렉터): Throuput Collector라고도 불리는 이 GC는 직렬 가비지 컬렉터와 유사하지만,

병렬 스레드 처리로 가비지 수집속도를 높입니다.

성능이 최우선이고, 일시 중지 시간 요구사항이 없거나 1초 이상의 일시 중지가 허용되는

애플리케이션의 경우 해당 병렬 컬렉션을 선택하는것을 권장합니다.

 

- G1 GC (Garbage-First): 이전 가비지 컬렉션과 달리 연속된 블록 단위가 아닌 영역 단위(1MB  ~ 32MB)

힙을 관리합니다. 현재 기본적으로 적용되는 컬렉션입니다. 힙 크기는 최대 수십 GB 이상으로 권장됩니다.
응답 시간이 전체 처리량 및 가비지 수집보다 더 중요하면서 (일시정지를 짧게 유지)
병렬로 처리하기 를 원하면

해당 컬렉션을 선택하는 것을 권장합니다. 

 

- Z GC: 응답 시간이 우선 순위가 최우선(10밀리초 내외)인 경우 해당 컬렉터를 이용합니다.

시스템 리소스가 많이 필요합니다. 수백MB에서 16TB 까지 힙크기 까지 잘 작동합니다.

 

 

결론: 범용으로 사용할 수 있는 GC는 없습니다. 메모리적인 요소나, CPU 스레드 수 에 따라

시작 GC를 정할 수 있어도 힙의 크기, 애플리케이션에서 유지 관리하는 라이브 데이터양,

사용가능한 프로세서 수 , 속도, 목표에 따라 다르기 때문에  직접 비교해보고 진행하여야합니다.
Oracle 가이드 문서에서 지향하는 방향은 우선 힙의 크기와 세대(Generation) 크기를 조정해보고,

만족할만한 성능이 나오지 않는 경우 컬렉터를 변경하는 방법을 사용합니다.

 

성능을 어떤식으로 비교하는지 참고할만한 링크: Java 응용 프로그램 성능 : 최상의 GC를 선택하는 방법 | 아카마스 (akamas.io)

 

 

GC 반복처리시 S0 -> S1 & S1 -> S0 이 Jstat으로 관찰이 안되는 이유?

이는 GC 컬렉션에 따른 문제이며, 기본 GC사용에는 G1 GC가 사용되는데 이는 S0와 S1가

구분되어 사용되지 않습니다.

G1 GC 사용시

 

S1C만 영역할당이 되어있습니다.

Parerall GC 사용시

 

S0C, S1C모두 할당되어있으며, 번갈아면서 사용중입니다.

 

 

GC 모니터링

GC를 모니터링하기 위해서는 데이터를 수집하여야 하는데 수집방법에는 여러가지가 존재합니다.

1. GC 로깅을 통한 수집

-Xlog:gc*:file=gc.log:time,level,tags 옵션을 통해 로그를 파일화 할 수 있는데,

이러한 데이터를 일일히 확인하기는 힘드므로 통계처리를 해주는 프로그램이 필요합니다.

 

2. JMX를 통한 수집

로컬의 경우 visualVM을 통해 수집 및 모니터링이 가능하며, 원격을 이용하는 경우 포트를 개발하여 데이터를 수집하여야합니다.

 

3. JFR을 통한 수집

-XX:StartFlightRecording=filename=gc_analysis.jfr,duration=10m,settings=profile

위 옵션을 통해 마찬가지로 파일화 할 수 있는데, 애플리케이션 시동 후 N초간 jvm의 동작을 기록하는 행동을 합니다.

 

아래는 위의 OOM을 발생시키는 코드를 JFR을 통해 4가지 GC로 기록해본 것입니다.

 

Serial GC

  • DefNew: Logest Pause= 50.5ms, Sum Of Pause= 587ms, Count=32
  • SerialOld: Logest Pause= 164ms, Sum Of Pause= 699ms, Count=32

 

 

Parallel GC

 

  • ParallelScavenge : Logest Pause= 21.4ms, Sum Of Pause= 328ms, count = 56
  • ParallelOld : Logest Pause= 138ms, Sum Of Pause= 1208ms, count = 45

 

G1GC

  • G1Old  : Logest Pause= 2.23ms, Sum Of Pause= 556ms, count = 1124
  • G1New  : Logest Pause= 3.12ms, Sum Of Pause= 1445ms, count = 1340
  • G1Full  : Logest Pause= 76.5ms, Sum Of Pause= 2757ms, count = 402

 

Z GC

  • Z: Logest Pause= 0.056ms, Sum Of Pause= 7.51ms, count = 207

 

아래로 점차 내려오면서 일시정지 처리시간(최대)가 줄어듬을 확인 할 수 있었는데,

G1GC의 경우 총 일시정지시간은 큰 폭으로 늘어났습니다.

 

이렇게만 비교해면 Z GC가 가장 효율적이고 합리적인 선택인데 과연 그러한지 다른 지표도 같이 검사해보았습니다.
2분은 너무 적은것 같아 10분씩 측정하였습니다.

 

 

Serial GC 의 CPU 사용률
Serial GC 사용시의 힙 변화

 

 

Parallel GC의 CPU 사용률

 

Parallel GC의 힙변화

 

 

G1 GC CPU 사용률 변화

 

G1 GC 힙 변화

 

 

Z GC CPU 사용률

 

Z GC 힙 변화

 

CPU 사용률

Serial GC와 Z-GC가 가장 적은 사용률이 나왔다. VisualVM에서 CPU 사용률은 CPU 코어들의 평균 사용률이기

때문에 단독 Core를 사용하는 Serial GC의 경우 적게 나올 수 밖에 없습니다.

파형 및 사용률로 보자면 Serial  GC > Z GC > G1 GC > Pararell GC순으로 좋았습니다.  

 

Heap 메모리 

힙 메모리 파형을 보면 눈에 띄는 것은 G1GC이다. 위에 나온 GC 회수만큼 파형 간격도 좁다는 것을 알 수 있고,

메모리가 순식간에 차는 경우 많은 메모리 확보를 하는데 어려워진다는 것은 알 수 있습니다.

Max Heap사이즈에 가까워지지 않는

Pararell GC > Serial GC >= Z GC > G1 GC 순으로 안정적인 것으로 보였습니다.

 

마지막으로 10분간의 GC시간을 한번더 보았는데.

GC 유형 평균 Full GC 일시정지 시간 총 GC 일시 정지 시간
Serial GC 17.5 ms 5,517 ms
Pararell GC 29.2 ms 12,013 ms
G1 GC 7.4 ms 3,837 ms
Z GC 0.029ms 31.7 ms

 

결과가 위와의 반대로 Serial GC가 Parerall GC가 더 좋게 나왔는데, 병렬의 오버헤드가 더 커서 싱글 스레드 성능보다

안좋을 수 있다는 결과를 보여준 것 같습니다.

 

만약 운영서버에서 위와 같은 그래프가 주어진 경우 저는 Z GC를 사용할 것 입니다.

안정적인 메모리 확보, CPU 사용률, GC 일시정지시간, 처리량 확보에 대해 안 쓸 이유가 전혀 없어보입니다.

 

정리

GC를 변경하거나 옵션 튜닝을 하게되는 경우 꼭 모니터링 비교를 통해 선택합시다. 

위에 Serial GC와 Pararell GC의 일시정지 시간이 역전 된것처럼

애플리케이션 성격, 하드웨어에 따라서 성능은 달라질 수 있습니다.

 

GC를 선택하는 단계는 아래와 같이

애플리케이션에서 문제가 있는 것이 아닌지

옵션으로 튜닝하여 개선될 수 있는 사항이 아닌지

확인 후 선택하는것이 어떤가 생각해보았습니다.

 

'JAVA공부 > JVM' 카테고리의 다른 글

성능 시뮬레이션 - ② 가상 스레드  (0) 2024.11.03
String Pool  (1) 2024.10.06
자바 Heap Dump  (0) 2022.11.04
GC 튜닝  (0) 2022.01.14
Garbage Collection 모니터링  (0) 2022.01.12
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함