Java

[Java] Garbage Collection

DH_0518 2024. 3. 10. 01:00

가비지 컬렉션은 자바의 메모리 관리 방법 중의 하나로, 메모리 누수를 막기 위해 JVM의 Garbage Collector가 사용하지 않는 객체를 삭제하는 것을 의미한다. 가비지 컬렉션에 대해 알아보기 전에 메모리 누수를 먼저 알아보자

 

 

 

Memory Leak (메모리 누수)

 

메모리 누수란 "동적으로 할당한 메모리가 free(할당 해제)될 수 없는 상태"를 의미한다. 즉, 어딘가에 할당되어 있던 메모리가 더이상 사용자가 제어하거나 접근할 수 없어서 할당된 메모리를 회수할 수 없는 상태를 의미한다. 다음 코드를 보면 이해가 빠를것이다.

char *a = malloc(20);    // 포인터 변수 a에 동적 메모리 할당
char *b = malloc(10);    // 포인터 변수 b에 동적 메모리 할당

b = a;    // b가 가지고 있던 주소값을 a에 할당

free(a);  // 포인터 a에 할당한 20바이트의 메모리를 해제
free(b);  // 포인터 b는 a의 주소를 가르키므로, 처음 b에 할당한 10바이트의 메모리가 아닌 a에 할당한 20바이트 메모리 해제

// 따라서 포인터 b에 할당했던 10바이트 메모리가 할당 해제 될 수 없는 상태가 됨(memory leak 발생)

 

위의 코드에서 알 수 있듯이, 개발자가 더이상 처음 포인터 b에 할당되었던 10byte의 메모리를 회수할 수 없는 상태가 되었다.

그렇다면 이게 왜 문제가 되는것일까?

 

 

우리가 사용할 수 있는 메모리는 한정되어있다(실제로 우리가 사용할 수 있는 메모리는, 컴퓨터에 장치된 물리적 메모리 만큼이다. 물론 가상 메모리 덕분에 더 많이 사용할 수 있긴 하지만, 이는 실제 RAM이 늘아는게 아니라서 염두하지 않겠다). 이러한 측면에서 메모리 누수를 생각해보자.

이전 예시의 코드처럼 계속해서 메모리를 회수할 수 없는 상태가 쌓여간다 생각해보자. 분명 우리의 PC에서 사용할 수 있는 메모리 용량은 정해져있는데, 할당되는 메모리가 점점 많아지면서 메모리 사용량이 계속해서 증가 할 것이다. 이는 결국 시스템의 메모리가 부족해져 OS가 프로그램을 강제로 종료시키거나 메모리 할당 자체를 실패하게 된다(프로그램을 실행할 수 없다는 것이다).

 

이러한 최악의 상황을 방지하기 위해서 우리는 사용하지 않는 메모리를 할당 해제해야할 의무가 있다. 실제로 C, C++ 에서는 개발자가 직접 메모리 할당을 해제해주어야하고, 프로그램 종료 전에 free를 하지 않았다거나, free 이전에 에러가 발생하는 등의 문제 때문에 메모리 누수가 일어나게 된다.

 

 

그.러.나

우리의 Java에는 'Garbage Collector'라는 천조국의 패트리어트 지대공 미사일에 버금가는 훌륭한 메모리 추적 기능이 존재한다. 이 가비지 컬렉터는 동적으로 할당된 메모리를 추적하고 이를 더 이상 사용하지 않을 때 자동으로 해제하는 메커니즘을 가지고 있기에, 개발자가 실수든 고의든 메모리를 할당 해제하지 않더라도 자동으로 해제시켜서 OutOfMemoryError에 의해 서버가 다운되어버리는 대참사를 막아준다.

이렇게 개발자에게 심신의 안정을 가져다주는 중요한 역할을 하는 가비지 컬렉션에 대해서 더 자세히 알아보자.

 

 

 

 

 

 

Garbage Collection이란?

 

앞서 말했듯이 Java에는 메모리 누수를 막기 위해, 객체를 생성한 후 사용하지 않는 객체의 메모리를 자동으로 할당 해제 해주는 Garbage Collector가 존재한다. Java에서 Garbage Collector는 JVM의 구성요소중 하나이고, Garbage Collector에 의해 자동으로 메모리가 할당 해제되는 메모리 관리 기법을 Garbage Collection이라고 한다.

 

Garbage Collection

  • 메모리 관리 기법중의 하나
  • JVM의 Heap 영역에서 동적으로 할당했던 메모리 중, 필요없게 된 메모리 객체(garbage)를 모아 주기적으로 제거하는 프로세스를 말함
  • Garbage Collection은 JVM의 Garbage Collector가 수행한다
  • STW(Stop-The-World)
    • GC를 수행하기 위해서 JVM 애플리케이션 실행을 멈추는 것을 말한다
    • 모든 객체의 참조 관계를 추적하고, 유효한 객체들과 그렇지 않은 객체들을 식별하여 메모리를 회수해야 하기 때문에, JVM 애플리케이션 실행이 멈추게 된다
    • Java는 GC를 실행하는 스레드를 제외한 모든 스레드의 작업을 멈추기 때문에, GC를 완료한 이후 중단했던 작업을 다시 실행할 수 있어서 오버헤드가 발생한다. 이 오버헤드를 줄이기 위한 다양한 GC 튜닝이 존재한다

Inpa Dev: 가비지 컬렉션 동작 원리 & GC 종류

 

 

Garbage (가비지 컬렉션 대상)

  • GC는 특정 객체가 garbage인지 아닌지를 판단하기 위해 도달성, 도달능력(Reachability)라는 개념을 적용한다
  • 객채는 Reachable과 Unrechable로 구분된다
    • Reachable: 객체가 참조되고 있는 상태
    • Unrechable: 객체가 참조되고 있지 않은 상태, GC의 대상이 된다

Inpa Dev: 가비지 컬렉션 동작 원리 & GC 종류

 

 

 

GC가 일어나는 Heap 메모리 구조

  • 동적으로 레퍼런스 데이터가 저장되는 공간으로, GC의 대상이 되는 공간
  • Heap 영역은, 다음의 두 가지 전제(객체는 대부분 일회성이며, 메모리에 오랫동안 남아있는 경우는 드물다)를 가지고 설계되었다
    • 대부분의 객체는 금방 접근 불가능한 상태(Unreachable)가 된다
    • 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다
  • 위의 두 가지 전제에 따라, 객체의 생존 기간을 기준으로 Young과 Old의 두 가지 영역으로 구분하여 설계하였다
    • Young 영역(Young Generation)
      • 새롭게 생성된 객체가 할당되는 영역
      • 대부분이 금방 Unreachable이 되기에 많은 객체가 Young 영역에 생성되었다가 사라진다
      • Young 영역에 대한 GC를 Minor GC라고 부른다
      • Young 영역 또한 Eden, Survivor 0, Survivor 1 으로 나뉘어진다 (객체는 eden -> survivor -> old 순으로 이동)
    • Old 영역(Old Generation)
      • Young 영역에서 Reachable 상태를 유지하여 살아남은 객체가 복사되는 영역
      • Young 영역보다 크게 할당되며, 영역의 크기가 큰 만큼 Garbage는 적게 발생한다
      • Old 영역에 대한 GC를 Major GC / Full GC 라고 부른다

Inpa Dev: 가비지 컬렉션 동작 원리 & GC 종류

 

 

 

 

GC의 메모리 해제 방식

  1. Mark : Root Space로부터 그래프 순회를 통해, 연결된 객체들을 찾아내어 각각 어떤 객체를 참조하고 있는지 찾아서 마킹한다
  2. Sweep : 참조하고 있지 않은 객체, 즉 Unreachable 객체를 Heap에서 제거한다
  3. Compaction : Sweep 후에 분산된 객체들을, Heap의 시작 주소로 모아 메모리에 할당된 부분과 그렇지 않은 부분으로 압축한다 (GC 종류에 따라서 선택적 실행)

Inpa Dev: 가비지 컬렉션 동작 원리 & GC 종류
Inpa Dev: 가비지 컬렉션 동작 원리 & GC 종류

 

 

GC의 메모리 해제 과정 (Minor GC, Major GC)

Minor GC

  1. 어떤 새로운 객체가 생성되면, Heap의 Young 영역중 Eden Space에 할당됨
  2. Eden Space가 가득차면 Minor GC가 실행
  3. Reachable 객체들은 aged된 이후(age가 1씩 증가) survivor 0로 이동되고, Unreachable 객체는 메모리에서 제거됨
    (이때 소요되는 시간은 대략 0.5s~1s)
  4. 다음 Minor GC때 마찬가지로 Unreachable 객체는 메모리에서 제거되지만, eden의 reachable 객체와 survivor 0의 reachable 객체 모두가 aged 되고 survivor 1로 이동함. 따라서 survivor 1에는, age가 다른 객체들이 존재하게 됨
  5. 그 다음 Minor GC가 수행되면, 마찬가지로 eden과 survivor 1의 모든 reachable 객체들은 aged 된 이후 survivor 0로 이동하게 됨
    즉, survivor 영역에는 서로 다른 age의 객체들이 존재하고, survivor 0와 survivor 1은 동시에 객체들이 존재할 수 없음
  6. 1~5가 반복되면서, reachable 객체들의 age가 age threshold(문지방)을 넘게되면, Young 영역에서 Old 영역으로 이동됨. 이렇게 young에서 old로 객체가 이동하는것을 promotion이라고 부름

Major GC

  1. Minor GC가 반복되면서 Old 영역에 aged 객체가 쌓이기 시작함
  2. Old 영역이 가득차면 Major GC가 실행
  3. Old 영역에 있는 모든 객체들을 검사하여, Unreachable 객체를 한꺼번에 삭제하게됨. 이때 Old 영역은 Young 영역에 비해 상대적으로 큰 공간을 가지고 있어서, Minor GC에 비해 많은 시간이 걸리게 됨(일반적으로 10배 이상의 시간)

 

 

 

 

GC 알고리즘

 

우리는 GC가 무엇인지, 왜 해야하는지, 어떻게 작동하는지를 알아봤다. 이렇게 만능처럼 보이는 GC에도 단점이 존재한다.

메모리가 언제 해제되는지 정확하게 알 수 없어 제어하기 힘들며, GC가 동작하는 동안 STW에 의해 오버헤드가 발생하기 때문이다.

이를 해결하기 위한 GC의 다양한 알고리즘을 알아보자

 

다음 GC 알고리즘은, 모두 설정을 통해 Java에 적용시킬 수 있다

 

Garbage Collection Algorithm

  1. Serial GC
    • 서버의 CPU 코어가 1개일 때 사용하기 위해 개발된 가장 단순한 GC
    • GC 쓰레드가 1개로 작동, 따라서 STW 시간이 가장 길다
    • Minor GC에는 Mark-Sweep을 사용, Major GC에는 Mark-Sweep-Compact를 사용
    • 보통 실무에서 사용하는 경우가 없다
  2. Parallel GC
    • Java 8의 디폴트 GC
    • Serial CG와 같은 알고리즘이지만, Minor GC를 멀티 쓰레드로 수행한다
    • Major GC는 여전히 싱글 쓰레드
  3. Parallel Old GC (Parallel Compacting Collector)
    • Parallel GC의 개선 버전
    • 새로운 GC 청소 방식인 Mark-Summary-Compact를 사용하여, Major GC도 멀티 쓰레드로 수행
  4. CMS GC (Concurrent MArk Sweep)
    • 어플리케이션 쓰레드와 GC 쓰레드가 동시에 실행되어, STW 시간을 최대한 줄이기 위해 고안되었음
    • CG 과정이 매우 복잡해서 다른 GC에 비해 CPU 사용량이 높음
    • 메모리 파편화 문제가 존재
    • Java 9부터 deprecated 되었고, Java 14에서는 사용 중지됨
  5. G1 GC (Garbage First)
    • CMS를 대체하기 위해 jdk 7에서 최초로 release됨
    • Java 9+ 버전의 디폴트 GC
    • 4GB 이상의 힙 메모리와 STW 시간이 0.5s 정도 필요한 상황에 사용 (Heap이 너무 작을경우 미사용 권장)
    • 기존처럼 Heap을 Young/Old로 나누는 것이 아니라, Region이라는 새로운 개념을 도입하여 사용
    • 전체 Heap을 Eden, Survivor, Old를 고정이 아닌 동적으로 부여하여 사용함
    • 메모리가 많이 차있는 영역(region)을 인식하는 기능을 통해, garbage로 가득찬 영역을 빠르게 회수하여 빈 공간을 확보하므로 GC 빈도가 줄어드는 효과를 얻게 됨
  6. Shenandoah GC
    • Java 12에 release
    • 레드 햇에서 개발한 GC
    • CMS가 가진 단편화와, G1이 가진 pause 이슈를 해결
    • 강력한 Concurrency와 가벼운 GC 로직으로, heap 사이즈에 영향을 받지 않고 일정한 pause 시간이 소요됨
  7. ZGC (Z Garbage Collector)
    • Java 15에 release
    • 대량의 메모리(8M ~ 16TB)를 low-latency로 처리하기 위해 디자인 되었음
    • G1의 region처럼, ZGC는 ZPage라는 새로운 영역을 도입. G1의 region은 고정인데 비해 ZPage는 2mb 배수로 동적으로 운영됨
    • 힙 크기가 증가하더라도 STW의 시간이 절대 10ms를 넘지 않음

 

 

 

 

 

 

 

 

 

 

 

 

 

Reference

 

Garbage Collection | 👨🏻‍💻 Tech Interview

Garbage Collection Goal Garbage Collection의 역할에 대해 설명할 수 있다. Garbage Collection의 메모리 해제 과정을 3단계로 설명할 수 있다. Generational Gabage Collection에 대해 설명할 수 있다. Generational Garbage Colle

gyoogle.dev

 

🌸 Memory Leak (메모리 릭, 메모리 누수)란?

◼︎ 결론 (summary)

80000coding.oopy.io

 

☕ 가비지 컬렉션 동작 원리 & GC 종류 💯 총정리

Garbage Collection(GC) 이란? 가비지 컬렉션(Garbage Collection, 이하 GC)은 자바의 메모리 관리 방법 중의 하나로 JVM(자바 가상 머신)의 Heap 영역에서 동적으로 할당했던 메모리 중 필요 없게 된 메모리 객

inpa.tistory.com

 

[Java]가비지 컬렉터(Garbage Collector)란?

JVM GC가 Heap에서 사용하지 않는 객체를 메모리를 해제해준다. 하지만 어떻게 동작하고 어떤 알고리즘을 사용 하는지 알아보지 못해서 글로 작성해봤다. 단순히 암기가 아닌 시각적인 사진들을

velog.io

 

[JAVA] Garbage Collection의 기초

이 글은 PC 버전 TISTORY에 최적화 되어있습니다. 서론 이전에 안드로이드 메모리 관리 관련하여 Strong, Soft, Weak Reference에 대해 알아보면서 GC에 대해 살짝 언급한 적이 있습니다. Java의 가비지 컬렉

itmining.tistory.com