본문 바로가기

Java

[JAVA] 가비지 콜렉션 (Garbage Collection, GC)

1. Garbage Collection(GC)이란?

자바의 메모리 관리 방법 중의 하나로 JVM(자바 가상 머신)의 Heap 영역에서 동적으로 할당했던 메모리 중 참조되지 않는 객체를 모아 주기적으로 제거하는 프로세스를 말한다.

C / C++ 언어에서는 수동으로 메모리 할당과 해제를 일일이 해줘야 했었다. Java에서는 가비지 컬렉터가 메모리 관리를 대신 해주므로 한정된 메모리를 효율적으로 사용할 수 있게 하고, 개발자 입장에서는 개발에만 집중할 수 있다는 장점이 있다.

그러나 자동으로 처리해준다고 해도 메모리가 언제 해제되는 지 정확하게 알 수 없어 제어하기 힘들며, GC가 동작하는 동안에는 프로그램 실행을 멈추기 때문에 오버헤드가 발생되는 문제점이 있다.

이를 Stop-The-World라고 한다. GC가 작동하는 동안 GC 관련 Thread를 제외한 모든 Thread는 멈추게 되어 서비스 이용에 차질이 생길 수 있다.

즉, GC가 너무 자주 실행되어도 소프트웨어의 성능에 문제가 될 수 있다.

따라서 어플리케이션의 사용성을 유지하면서 효율적인 GC를 실행하도록 최적화 하는 것이 중요하다. 이와 같이 GC를 최적화하는 과정을 GC 튜닝이라 하며 소프트웨어 성능 개선의 쟁점이 된다.

애플리케이션 구동 시 JVM 실행 옵션을 통해 메모리 영역과 GC 알고리즘에 대한 옵션을 적용할 수 있으므로 GC 최적화를 위해서는 JVM의 메모리 구조와 이 안에서 어떻게 GC가 발생하는 지, 그리고 다양한 방식으로 동작하는 여러 GC 알고리즘을 알아야 한다.

지난 포스팅에서는 JVM의 전체 메모리 구조에 대해 알아보았고 이번에는 JVM 메모리의 구성 요소 중에 Garbage Collection의 대상이 되는 Heap 메모리에 대해 자세히 알아보자.

 

[JAVA] JVM 메모리 구조(Runtime Data Area)

+ JVM에 관한 이전 포스팅 [JAVA] JVM(Java Virtual Machine)1. JVM(Java Virtual Machine)이란?자바 코드로 작성된 프로그램을 실행하면 이것을 컴퓨터가 이해할 수 있고, 어떤 운영체제에서도 실행 될 수 있는 기

zisooya.tistory.com

 


2. JVM의 Heap 메모리 구조

JVM의 힙 영역은 참조 데이터가 동적으로 할당되는 공간으로서, Garbage Collection의 대상이 되는 공간이다.

효율적인 메모리 관리를 위해, Heap 영역은 객체의 생존 기간에 따라 물리적으로 Young Generation과 Old Generation 2가지 영역으로 나뉜다.

Young Generation은 새로운 객체들이 할당되는 영역이고, Old Generation은 Young Generation에서 오랫동안 살아남은 객체들이 승격되는 영역이다.

 

Young Generation

  • 새로운 객체가 Young Generation에 생성되었다가 더이상 참조되지 않는 객체는 사라진다.
  • Young Generation에서 일어나는 GC를 Minor GC라고 함. Old 영역에 비해 상대적으로 작은 공간이라 메모리 상의 객체를 찾아 제거하는 데에 적은 시간이 걸리므로 Minor GC라고 칭한다.
  • 효율적인 GC를 위해 Young 영역은 또 다시 3가지 영역(Eden, survivor 0, survivor 1)으로 나뉜다.

Eden  

  • new를 통해 새로 생성된 객체가 위치
  • 정기적인 GC 후 살아남은 객체들은 Survivor 영역으로 보냄

survivor 0 / survivor 1

    • 최소 1번의 GC 이상 살아남은 객체가 존재하는 영역
    • Survivor 영역에는, Survivor0 또는 Survivor1 둘 중 하나는 꼭 비어있어야 한다는 규칙이 있다.
      이는 GC 발생 시 Eden + 사용 중인 Survivor 영역의 살아있는 객체들을 다른 비어 있는 Survivor 영역으로 복사하는 객체 복사(Copying) 방식 때문이다.

Old Generation

  • Young Generation에서 오랫동안 살아남은 객체가 복사되는 영역
  • Young Generation보다 크게 할당되며, 영역의 크기가 큰 만큼 Garbage는 적게 발생한다.
  • Young Generation보다 크게 할당되는 이유는 Young영역의 수명이 짧은 객체들은 큰 공간을 필요로 하지 않고, 큰 객체들은 Young 영역이 아니라 바로 Old 영역에 할당되기 때문이다.
  • Old Generation에 대한 GC를 Major GC 또는 Full GC라고 한다.
GC 종류 Minor GC Major GC(Full GC)
대상 영역 Young Generation Old Generation
실행 시점 Eden 영역이 꽉 찼을 때 Old 영역이 꽉 찼을 때
실행 속도 빠르다 느리다

3. 가비지 컬렉션 방식 - Mark And Sweep

 

JVM은 위와 같은 힙 메모리의 구조를 가지고 Mark and Sweep이라는 과정을 통해 GC를 수행한다.

Mark and Sweep이란 GC에 사용되는 내부 알고리즘이다. 가비지 컬렉션이 될 대상 객체를 식별(Mark)후 제거(Sweep)하고, 객체가 제거되어 파편화된 메모리 영역을 앞에서부터 채워나가는 작업(Compaction)을 수행한다.

 

Mark And Sweep 작업 과정

  1. 처음 생성된 객체는 Yong Generation 영역의 Eden 영역에 위치
  2. 객체가 계속 생성되어 Eden 영역이 꽉차면 Minor GC가 실행
  3. Mark 동작을 통해 사용되는 rechable 객체를 탐색
  4. Eden 영역에서 살아남은 객체는 1개의 Survivor 영역으로 이동
  5. Eden 영역에서 사용되지 않는 unreachable 객체의 메모리를 해제(Sweep)
  6. 살아남은 모든 객체들은 age(Survivor 영역에서 객체의 객체가 살아남은 횟수)값이 1씩 증가
    (만약 age 값이 임계값에 다다르면 Old 영역으로 이동하는 promotion이 발생하고, 이 과정이 반복되어 Old영역이 부족해지면 Major GC(Full GC)가 발생)
  7. 또다시 Eden 영역에 신규 객체들로 가득 차게 되면 다시한번 minor GC 발생하고 mark 한다.
  8. marking한 객체들을 비어있는 Survival 영역으로 이동하고 sweep
  9. 다시 살아남은 모든 age(Survivor 영역에서 객체의 객체가 살아남은 횟수)값이 1씩 증가
  10. 반복

4. 가비지 컬렉션 알고리즘

GC가 동작하면 Thread가 멈추고 Mark and Sweep 작업을 해야 해서 CPU에 부하를 주기 때문에 멈추거나 버벅이는 Stop-The-World 문제가 발생한다.

이때문에 GC를 최적화하기 위해 GC 알고리즘이 개발 되었는데 이것들을 JVM 실행 옵션을 통해 적절히 적용하면 성능 개선에 도움이 된다.

 

Serial GC

  • 싱글 스레드로 동작하는 GC로, Stop-the-world 시간이 가장 길다.
  • 적용 대상: 실무에서 잘 사용하지 않는다. 메모리가 적고 CPU 코어가 1개인 환경
  • 옵션: XX:+UseSerialGC

Parallel GC

  • CPU 개수만큼의 스레드를 사용하여 Young 영역의 Minor GC를 멀티스레드로 수행 (Old 영역은 여전히 싱글 스레드)
  • 옵션을 통해 GC를 수행할 스레드의 갯수 등을 설정 가능 java -XX:+UseParallelGC -jar Application.java
  • Serial GC에 비해 stop-the-world 시간 감소
  • 멀티코어 CPU 환경. 응답 시간보다 처리량이 중요한 애플리케이션에 적합
  • 옵션: XX:+UseParallelGC

Parallel Old GC (Parallel Compacting Collector)

  • Parallel GC의 개선된 버전으로, Young 영역뿐만 아니라 Old 영역의 Full GC에서도 멀티 스레드로 GC를 수행하여 Stop-the-world 시간을 줄임
  • GC 후 살아남은 객체들을 한쪽으로 몰아 연속적인 빈 공간을 확보하는 Compacting(압축)을 수행하여 단편화를 방지
  • 대용량 데이터 처리가 필요한 애플리케이션에서 적합
  • 옵션: XX:+UseParallelOldGC

CMS GC(Concurrent Mark Sweep)

  • 애플리케이션의 스레드와 GC 스레드를 동시에 실행되어 Stop-the-world 시간을 줄임
  • Mark-Sweep 방식을 사용하여 Old 영역의 Full GC를 수행
  • 복잡한 과정 중 일부 과정(초기 마킹, 리마킹)은 Stop-the-world가 발생하며 CPU 사용률 증가하고, 메모리 파편화 문제가 발생 가능하다는 단점으로 인해 Java9 버젼부터 deprecated되었고 JDK 14에서 제거됨
  • 옵션: XX:+UseConcMarkSweepGC

G1 GC (Garbage First)

  • Java 9의 기본 GC
  • 4GB 이상의 힙 메모리, Stop-the-World 시간이 0.5초 정도 필요한 상황에 사용 (Heap이 너무작을경우 미사용 권장)
  • 압축(Compaction)을 수행하여 단편화 문제 해결
  • Heap 영역을 물리적으로 고정된 Young / Old 영역으로 나누지 않고, 전체 Heap 영역을 여러 개의 영역(region)으로 분할하여 처리하는데 이 때 각 region의 역할은 메모리 사용 패턴과 GC의 필요성에 따라 Eden, Survivor, Old 중 유동적으로 할당된다.
  • 또한, 이전 GC들은 Young Generation의 객체들이 살아남으면 Eden → Survivor0 → Survivor1로 순차적으로 이동했지만, G1 GC에서는 순차적인 이동 없이 더 효율적인 위치로 객체를 재할당한다. 예를 들어, Survivor1에 있는 객체가 Eden 영역으로 이동하는 것이 더 효율적이라고 판단되면, Eden 영역으로 이동시킨다.
  • Low-latency(낮은 응답 시간) 및 대용량 메모리가 필요한 서버 애플리케이션에 적합
  • 옵션: XX:+UseG1GC

Shenandoah GC

  • JDK 12에 release
  • 애플리케이션 실행과 동시에 GC 대부분의 작업을 수행
  • CMS 및 G1 GC보다 pause 시간이 짧음
  • 힙 크기와 무관하게 일정한 응답 시간을 유지
  • 실시간 시스템, 대용량 애플리케이션, 응답 시간이 중요한 서비스에 적합
  • 옵션: XX:+UseShenandoahGC

ZGC (Z Garbage Collector)

  • JDK 11에 release
  • 대용량 힙(수십 GB~TB 단위)에서 초저지연(Low-Latency)을 목표로 하는 GC로, GC Pause Time을 1~2ms 수준으로 유지
  • 대부분의 GC 작업을 애플리케이션과 동시에 수행
  • GC 자체가 객체 참조를 추적하는 방식 사용(Color Pointers)
  • 초대형 힙을 사용하는 애플리케이션(클라우드, 빅데이터, AI 등)에 적합
  • 옵션: XX:+UseZG