GC 튜닝

2021. 8. 12. 00:25Java/Garbage Collection

GC 튜닝을 해야하는 이유

일반적으로 Java에서 생성된 객체는 GC가 처리해서 지웁니다. 생성된 객체가 많을수록 GC가 처리해야 하는 대상도 많아지고, GC를 수행하는 횟수도 증가합니다. 즉, 운영하고 만드는 시스템이 GC를 적게 하도록 하려면 객체 생성을 줄이는 작업을 먼저 해야합니다. 만약 애플리케이션 메모리 사용도 튜닝을 많이 해서 어느 정도 만족할 만한 상황이 되었다면, 본격적으로 GC 튜닝을 합니다.

GC 튜닝의 목적

Old 영역으로 넘어가는 객체의 수 최소화하기

최신 Java이 아닌 구 버전의 Java는 Eden 영역에서 객체가 처음 만들어지고, Survivor 영역을 오가다가, 끝까지 남아 있는 객체를 Old 영역으로 이동합니다. 간혹, Eden 영역에서 만들어지다가 크기가 커져서 Old 영역으로 바로 넘어가는 객체도 존재합니다. Old 영역의 GC는 New 영역의 GC에 비해 상대적으로 시간이 오래 소요되기 때문에 Old 영역으로 이동하는 객체의 수를 줄이면 Full GC가 발생하는 빈도를 많이 줄일 수 있습니다. Old 영역으로 넘어가는 객체의 수를 줄인다는 말을 잘못 이해하면 객체를 마음대로 New 영역에만 남길 수 있다고 생각할 수 있지만, 그렇게는 할 수 없습니다. 하지만 New 영역의 크기를 잘 조절함으로써 큰 효과를 볼 수는 있습니다.

Full GC 시간 줄이기

Full GC의 실행 시간은 상대적으로 Minor GC에 비해 깁니다. 그래서 Full GC 실행에 시간이 오래 소요되면 연계된 여러 부분에서 타임아웃이 발생할 수 있습니다. 그렇다고 Full GC 실행 시간을 줄이기 위해서 Old 영역의 크기를 줄이면 자칫 OutOfMemoryError가 발생하거나 Full GC 횟수가 늘어납니다. 반대로 Old 영역의 크기를 늘리면 Full GC 횟수는 줄어들지만 실행 시간이 늘어납니다. Old 영역의 크기를 적절하게 설정해야 합니다.

GC 튜닝의 절차

GC 상황 모니터링

GC 상황을 모니터링하며 현재 운영되는 시스템의 GC 상황을 확인해야 합니다.

모니터링 결과 분석 후 GC 튜닝 여부 결정

GC 상황을 확인한 후에는, 결과를 분석하고 GC 튜닝 여부를 결정해야 합니다. 분석한 결과를 확인했는데 GC 수행에 소요된 시간이 0.1 ~ 0.3초 밖에 안 된다면 굳이 GC 튜닝에 시간을 낭비할 필요는 없습니다. 하지만 GC 수행 시간이 1 ~ 3초, 심지어 10초가 넘는 상황이라면 GC 튜닝을 진행해야 합니다. 만약, 메모리를 1GB나 2GB로 지정했을 때 OutOfMemoryError가 발생한다면, 힙 덤프를 따서 그 원인을 확인하고, 문제점을 제거해야만 합니다.

GC 방식/메모리 크기 지정

GC 튜닝을 진행하기로 결정했다면 GC 방식을 선정하고 메모리의 크기를 지정합니다. 이때 서버가 여러대라면 여러 대의 서버에 GC 옵션을 서로 다르게 지정해서 GC 옵션에 따른 차이를 확인하는 것이 중요합니다.

결과 분석

GC 옵션을 지정하고 적어도 24시간 이상 데이터를 수집한 후에 분석을 실시합니다. 운이 좋으면 해당 시스템에 가장 적합한 GC 옵션을 찾을 수 있습니다. 그렇지 않다면 로그를 분석해 메모리가 어떻게 할당되는지 확인해야 합니다. 그 다음에 GC 방식/메모리 크기를 변경해 가면서 최적의 옵션을 찾아 나갑니다.

GC 상황 모니터링 및 결과 분석하기

운영중인 WAS의 GC 상황을 확인하는 가장 좋은 방법은 jstat 명령어를 사용하는 것입니다.

이 중에서 YGC와 YGCT의 값을 확인합니다. 두 값을 나누면 0.05초라는 값이 나옵니다. 즉 Young 영역에서 GC가 수행되는데 평균 50ms가 소요되었다는 말입니다. 이 정도면 Young 영역의 GC는 신경쓰지 않아도 됩니다.

이번에는 FGCT와 FGC의 값을 확인합니다. 두 값을 나누면 19.68초라는 값이 나옵니다. 이러한 경우에는 GC 튜닝이 필요하다고 판단할 수 있습니다.

만약 모니터링 결과가 다음의 조건에 모두 부합한다면, GC 튜닝이 굳이 필요하지는 않습니다.

  • Minor GC의 처리 시간이 빠르다(50ms 내외)
  • Minor GC의 주기가 빈번하지 않다(10초 내외)
  • Full GC의 처리 시간이 빠르다(1초 내외)
  • Full GC의 주기가 빈번하지 않다(10분에 1회)

참고로 괄호 안의 값은 절대적인 기준이 아닙니다. 서비스의 상황에 따라 다라질 수 있습니다. 그러므로 이와 같은 값을 확인하고 서비스의 특성에 따라 GC 튜닝 작업을 진행할지 결정합니다.

한 가지 주의할 점은 GC가 수행되는 횟수도 확인해야한다는 점입니다. 만약 New 영역의 크기가 너무 작게 잡혀 있다면 Minor GC가 발생하는 빈도도 매우 높을 뿐만 아니라 Old 영역으로 넘어가는 객체의 수도 증가하게 되어 Full GC 횟수도 증가합니다. 따라서 각 영역을 얼마나 점유하여 사용하는지도 확인해야 합니다.

메모리 크기 지정

메모리 크기와 GC 발생 횟수, GC 수행 시간의 관계는 다음과 같습니다.

  • 메모리의 크기가 크다면 GC 발생 횟수는 줄어들지만 GC 수행 시간은 길어진다
  • 메모리의 크기가 작으면 GC 수행 시간은 적어지지만 GC 발생 횟수는 증가한다

일반적으로 Full GC 후에 남아 있는 메모리가 300MB 정도라면 300MB(기본 사용) + 500MB(Old 영역용 최소) + 200MB(여유 메모리)를 감안하여 1GB 정도로 지정하는 것이 좋습니다. 즉, Old 영역을 위해 500MB 이상 여유가 있는 공간을 지정해야 한다는 말입니다.

메모리 크기를 지정할 때 NewRatio 또한 필수로 지정해야 합니다. NewRatio는 New 영역과 Old 영역의 비율을 말합니다. New 영역의 크기가 작으면 Old 영역으로 넘어가는 메모리의 양이 많아져서 Full GC도 잦아지고 시간도 오래 걸립니다.

728x90

'Java > Garbage Collection' 카테고리의 다른 글

G1 GC  (0) 2021.10.14
ZGC  (0) 2021.10.10
Shenandoah GC  (0) 2021.10.09
GC 모니터링  (0) 2021.08.13