본문 바로가기

프로그래밍 언어/Java

GC(Garbage Collector)

Java로 실행되는 Application들은 모두 JVM위에서 실행된다.

따라서 JVM이 작동하는데 있어서 메모리 구조와 GC는 애플리케이션의 응답 시간과 성능에 밀접한 관계를 맺고 있다.

 

GC란?

  • GC(Garbage Collection)는 자바 애플리케이션에서 사용하지 않는 메모리를 자동으로 수거하는 기능을 말한다.
  • C/C++ 같은 언어는 메모리를 할당하고 직접 해제해야했지만, 자바에서는 GC를 이용하여 개발자들이 메모리 관리를 비교적 신경쓰지 않아도 된다.

JVM의 메모리 영역

  • GC의 동작 방법을 이해하려면, 먼저 자바의 메모리 구조를 이해할 필요가 있다.
  • 일반적으로 애플리케이션에서 사용되는 객체는 오래 유지되는 객체보다 잠시 사용되는 경우가 많다.

  • 자바에서는 크게 두 영역으로 메모리를 구분한다.
  • Young 영역과 Old 영역인데, Young 영역은 생성된지 얼마 되지 않은 객체들을 저장하는 장소이고 Old 영역은 생성된지 오래된 객체를 저장하는 장소이다.

영역설명

New/Young 영역 : 이 영역은 객체가 생성되자마자 저장하는 영역이고 생성된지 얼마 안된 객체가 저장되는 영역이다. 시간이 지나고 우선순위에 따라서 Old 영역으로 옮겨진다.

Old 영역 : New/Young 영역에서 저장되었던 객체중에 오래된 객체들이 이동되어 저장되는 영역이다.

Perm 영역 : class, Method등의 코드가 저장되는 영역으로, JVM에 의해 사용되어진다.

JVM이 메모리를 관리하는 방식

Minor GC

  • 먼저 New/Young 영역을 Minor GC 라고 부른다. New/Young 영역은 Eden / Survivor 이라는 두 영역으로 또 나뉘게 된다.
  • Eden 영역은 자바 객체가 생성되자마자 저장되는 곳이다. 이렇게 생성된 객체는 Minor GC가 발생할 때 Survivor 영역으로 이동하게 된다.
  • Survivor 영역은 Survivor1과 Survivor2 두 영역으로 나뉘는데, Minor GC가 발생하면 Eden과 Survivor1에 활성 객체를 Survivor2로 복사한다.
  • 활성이 아닌 객체는 자연스럽게 Survivor1에 남아있게 되고, Survivor1과 Eden 영역을 클리어 한다. (결과적으로 활성 객체만 Survivor2)로 이동하게 된 것이다.
  • 다음번 Minor GC가 발생하면 같은 원리로 Eden과 Survivor2 영역에서 활성 객체를 Survivor1으로 이동시키게 된다. 계속 이런 방식을 반복하면서 Minor GC를 수행한다.
  • 이렇게 Minor GC를 수행하다가 Survivor 영역에서 오래된 객체는 Old 영역으로 옮기게 된다.
  • 이러한 방식의 GC 알고리즘을 Copy & Scavenge 라고 한다. 이 방식은 속도가 매우 빠르며 작은 크기의 메모리를 콜렉팅하는데 매우 효과적이다.
  • Minor GC의 경우에는 자주 일어나기 때문에 GC에 걸리는 시간이 짧은 알고리즘이 적합하다.

FULL GC

  • Old 영역의 가비지 컬렉션을 Full GC 라고 부르며 Full GC에 사용되는 알고리즘을 Mark & Compact라고 한다.
  • Mark & Compact 알고리즘은 객체들의 참조를 확인하면서 참조가 연결되지 않은 객체를 표시한다. 이 작업이 끝나면 사용되지 않는 객체를 모두 표시하고 이 표시된 객체를 삭제한다.
  • Full GC는 속도가 매우 느리며, Full GC가 일어나는 도중에는 순간적으로 자바 애플리케이션이 멈춰버리기 때문에, Full GC가 일어나는 정도와 시간은 애플리케이션의 성능과 안정성에 아주 큰 영향을 미친다.

GC가 중요한 이유

  • 가비지 컬렉션 중에서 마이너 GC의 경우에는 보통 0.5 이내에 끝나기 때문에 큰 문제가 되지 않지만, 그러나 FULL GC의 경우에는 자바 애플리케이션이 멈춰 버리기 때문에, 문제가 될 수 있다.
  • 멈추는 동안 사용자의 요청이 큐에 들어있다가, 순간적으로 요청이 한꺼번에 들어오기 때문에 과부하에 의한 여러 장애를 만들 수 있다.
  • 따라서 원활한 서비스를 위해서는 GC가 어떻게 일어나게 하느냐가 시스템의 안정성과 성능에 큰 변수로 작용할 수 있다.

다양한 GC 알고리즘

앞에서 설명한 방식 말고도 다양한 GC 방법을 제공하고 있다. 방식은 다음과 같다.

  • Default Collector
  • Concurrent GC for old generator

Default Collector

  • 앞에서 설명했던 전통적인 GC 방식으로, Minor GC로 Scanvenge를 Full GC로 Mark & Compact 알고리즘을 사용한다.

Concurrent GC

  • Full GC 즉, Old 영역을 GC하는데 시간이 길고 애플리케이션이 순간적으로 멈춰버리므로, 애플리케이션이 멈추는 현상을 최소화 하는 GC 방식이다.
  • Full GC에 소요되는 작업을 애플리케이션을 멈추고 진행하는 것이 아니라, 일부는 애플리케이션이 돌아가는 단계에서 수행하고 최소한의 작업만을 애플리케이션이 멈췄을 때 수행하는 방식이다.

GC의 로그 수집하는 방법

  • JVM에서는 GC 상황에 대한 로그를 남기고자 옵션을 제공하고 있다.
  • 자바 옵션에 -verbosegc 라는 옵션을 주면 되고, > 리다이렉션 명령어를 통해서 파일로 저장하고 분석할 수 있다.

  • 다음과 같이 애플리케이션이 실행되자마자 GC와 관련된 로그들이 출력된다.
  • 마이너 GC는 “GC"로 표기되고, FULL GC는 “FULL GC"로 표기된다.
  • 그 다음의 값은 HEAP SIZE BEFORE GC인데, GC전의 힙 사용량 (New/Young 영역 + Old 영역 + Perm 영역의) 크기를 나타낸다.
  • HEAP SIZE AFTER GC는 GC가 발생한 후의 HEAP 사용량이다. 마이너 GC가 발생하였을 때는 Eden과 Survivor 영역을 GC 하게 되므로 HEAP SIZE AFTER GC는 Old 영역의 용량과 유사하다.
  • 마지막 값은 GC에 소요된 시간을 나타낸다.

JVM GC 튜닝

STEP 1. 애플리케이션의 종류 및 튜닝 목표값을 설정

  • JVM 튜닝을 할 때 가장 중요한 것은 튜닝의 목표를 설정하는 것이다.
  • 메모리를 적게 사용하는 것이 목표인지, GC 횟수를 줄이는 것이 목표인지, GC에 걸리는 시간이 문제인지, 애플리케이션의 성능(Throughput or Response Time) 향상이 목표인지를 먼저 정하고 나서 목표치에 근접하도록 JVM 파라미터를 조정하는 것이 필요하다.

STEP 2. 힙크기와 Perm 크기 설정

  • -ms, -mx 옵션을 이용해서 힙 크기를 정한다. 일반적으로 서버 애플리케이션은 ms와 mx크기를 같게 하는 것이 메모리의 Growing와 Shrinking에 의한 불필요한 로드를 막을 수 있다.
  • ms와 mx 크기를 다르게 하는 경우는 애플리케이션의 시간대별 메모리 사용량이 급격하게 변화가 있는 애플리케이션에 효과적이다.

STEP 3. 테스트와 로그 분석

  • JVM 옵션에 GC로그를 수집하기 위한 -verbosegc 옵션을 적용한다.
  • nGrinder와 같은 스트레스 테스트 도구로 애플리케이션에 스트레스를 주어서, 그 로그를 수집한다.
  • 튜닝에 있어서 가장 중요한 것은 목표 산정이지만, 그만큼이나 중요한 것은 실제 튜닝한 파라미터가 애플리케이션에 어떤 영향을 주는지를 테스트 하는 것이다.

STEP 4. Perm 크기 조정

STEP 5. GC 수행 시간 분석

  • Full GC가 일어나는 횟수가 많아서 Old 영역을 늘려주면, Full GC 가 일어나는 횟수가 줄어들 것이고, 반대로 Full GC 수행 시간은 늘어날 것이다.
  • 특히 서버 애플리케이션은 Full GC가 일어날 때는 JVM 자체가 멈춰버리기 때문에 일정 시간동안 응답을 못하는 상태가 된다.
  • 서버 애플리케이션에서 Full GC가 적게 일어나게 하고, Full GC 시간을 양쪽 다 줄이려면 Old 영역의 메모리를 줄이고 여러 개의 인스턴스를 동시에 띄워서 로드 밸런싱을 해주면 부하가 분산된다.
  • 그렇게 되면, Full GC가 일어나는 횟수가 줄어들게 되며 Old 영역을 줄였기 때문에 Full GC가 수행되는 시간 또한 줄어든다.
  • 그리고 하나의 서버 인스턴스가 멈춰있는 동안 로드 밸런싱이 되는 다른 서버가 응답하고 있기 때문에 Full GC로 인하여 애플리케이션이 멈추는 상황에서 받을 영향을 최소화 할 수 있다.

STEP 6. 파라미터 변경

  • 각 영역의 허용 범위를 기준으로, Old 영역과 New 영역을 적절하게 조절한다.
  • Perm 크기와 New 영역의 배분(Eden, Survivor) 영역을 조정한다.
  • 가장 중요한 것은 Old 영역과 New 영역의 비율을 어떻게 조정하는가이다.

향상 포인트GC 알고리즘

Perfomance (속도) Parallel GC
Responsiveness (응답성) Concurrent GC
Responsiveness (응답성) Incremental GC
일반 Default GC

추가로 읽을 것들 : https://velog.io/@jsj3282/19.-GC-%ED%8A%9C%EB%8B%9D%EC%9D%84-%ED%95%AD%EC%83%81-%ED%95%A0-%ED%95%84%EC%9A%94%EB%8A%94-%EC%97%86%EB%8B%A4

'프로그래밍 언어 > Java' 카테고리의 다른 글

접근제어자  (0) 2022.07.13
Hash Map과 Hash Collision 문제  (0) 2022.07.10
자바 Transactional  (0) 2022.07.10
Interface가 왜 필요할까?  (0) 2022.07.09
동기화 처리 Synchronized  (0) 2022.06.12