Java

[Java] Garbage Collection

HEY__ 2024. 11. 9. 15:47
728x90

이 글은 공부를 하면서 알게 된 내용들을 기록하는 글 입니다. 오류나 고쳐야 할 사항들이 있다면 지적 부탁드립니다!

✅ Garbage Collection (GC)란?

`JVM(Java Virtual Machine)`에서 제공하는 메모리 관리 기법으로서, 프로그램에서 더 이상 사용되지 않는 메모리를 자동으로 회수합니다.

`JVM`의 Heap 영역에 동적으로 할당했던 메모리 객체(Garbage)를 파악한 후, 이를 주기적으로 메모리 할당 해제합니다.

 

C나 C++이 개발자가 직접 메모리를 할당하고 할당 해제하는 언어와 달리, GC가 있는 언어의 경우 개발자가 메모리 관리를 하지 않아도 됩니다.

그렇기 때문에 개발자는 메모리 관리와 같은 일에 신경쓰지 않고 개발에만 신경쓰면 되며,

`Memory leak(메모리 누수)` 혹은 `Dangling Pointer(이미 해제된 메모리를 가리키는 포인터)`와 같은 문제를 예방할 수 있습니다.

 

하지만 GC에도 단점이 존재하는데,

1️⃣ 메모리가 언제 해제되는지 정확하게 알 수 없기 때문에 제어하기 힘들고

2️⃣ GC가 동작하는 동안에는 GC를 실행하는 Thread를 제외한 모든 Thread의 동작이 멈추기 때문에 GC로 인한 Overhead가 발생할 수 있습니다.

 

2️⃣번을 다른 말로 `Stop-The-World` 라고 하는데, GC가 너무 자주 실행되면 `STW`로 인해 소프트웨어 성능 하락이 일어나기도 합니다. 


✅ Garbage Collection의 대상인지 확인하는 방법

🔥 도달성(Reachability) 검사

GC는 `Heap 영역`에 있는 다양한 Object(객체)들 중, 어떤 객체들을 Garbage로 판단하는 것일까요?

 

GC는 `도달성(Reachability) 검사`를 통해, 해당 객체가 Garbage인지 여부를 판단합니다.

출처: https://velog.io/@ddangle/Java-GC-Garbage-Collector%EC%97%90-%EB%8C%80%ED%95%B4

 

객체가 참조되고 있다면 Reachable로 구분되어 GC의 대상이 되지 않고,

객체가 참조되고 있지 않다면 Unreachable로 구분되어 GC의 대상이 됩니다.

 


GC의 동작 과정

🔥 GC의 기본 전제

1️⃣ 대부분의 객체는 생성 후 금방 죽는다 (Young)
2️⃣ 오래 살아남은 객체는 계속 살아남을 것이다 (Old)

 

 

🔥 메모리 구조

출처: https://www.youtube.com/watch?v=Fe3TVCEJhzo&list=WL&index=4

 

JVM 메모리의 Heap 영역은 다음과 같은 구조를 가지고 있습니다.

우선 크게 Young Generation, Old Generation 이렇게 두 개의 영역으로 나눌 수 있습니다.

 

1️⃣ Young Generation

Young Generation은 new()와 같은 연산자를 통해 생성된 객체가 할당(Allocation) 되는 영역입니다.

Young Generation에서 이루어지는 가비지 컬렉션을 `Minor GC`라고 부릅니다.

  • Eden
    새로 생성된 객체가 할당되는 영역
  • Survivor 1/0
    Minor GC에서 살아남은 객체가 이동하는 곳
    Survivor 0 혹은 Suvivor 1, 두 공간 중 하나는 꼭 비어있어야 하며, 0과 1의 영역을 왔다갔다하며 사용합니다.

 

2️⃣ Old Generation

Old Generation은 Young Generation에서 오랫동안 Reachable 상태를 유지한 객체가 넘어오는 곳입니다.

Young 영역보다 더 큰 공간을 가지고 있으며, 이 곳에서 이루어지는 가비지 컬렉션을 `Major GC` 혹은 `Full GC`라고 부릅니다.

 

 

 

🔥 Minor GC의 동작 과정

1️⃣ Minor GC (Young Generation)

① 새로 생성되는 객체는 모두 Young Generation의 `Eden` 영역에 생성

② 그러던 중, Eden 영역이 꽉 차게 되면 이 때 `Minor GC`가 실행

③ 이 때, 도달성(Reachability) 검사를 통해 Reachable한 객체의 경우 `Survivor` 영역으로 이동 

④ 이 과정에서 각 객체의 `age`가 1씩 증가합니다.

age는 객체가 survivor 영역에서 살아남은 횟수를 의미하며,
age가 임계값(age threshold)에 다다르면 Old Generation으로 이동하는 Promotion(승격)을 고려하게 됩니다.

 

🧐 Reachable한 객체들을 survivor 영역으로 이동하던 중에, survivor의 영역이 꽉차면 어떻게 되나요?

→ age threshold에 다다르지 않은 객체들을 Old 영역으로 옮기는 조기 승격(Premature Promotion)을 고려하게 됩니다.

 

🧐 그럼 모든 객체들이 Old 영역으로 조기 승격하게 되나요?

→ 아닙니다. 이 경우 객체의 크기를 기준으로 승격 여부를 확인하기도 합니다.

① 큰 객체부터 승격을 진행    ② 작은 객체는 Survivor 영역에 유지 하는 방향으로 진행합니다.

 

이 외에도 age threshold를 동적으로 설정하는 Dynamic Age Threshold, Eden와 Survivor의 비율을 동적으로 조절하는 Adapting size와 같은 방법이 있으며, 이는 GC의 종류마다 처리하는 방법이 달라집니다.

 

🔥 Major GC의 동작 과정

① Survivor 영역에서 age threshold에 도달한 객체는 Old 영역으로 이동

② 그러던 중, Old 영역이 꽉 차게 되면 `Major GC` 동작

③ 도달성(Reachability) 검사를 통해 Unreachable한 객체를 대상으로 선정

④ GC의 대상이 되는 객체들을 한 번에 삭제

 

Old Generation(영역)이 꽉 찼을 때 발생하며, Old 영역의 크기가 크기 때문에 소요 시간이 걸린다는 단점이 있습니다.

Old 영역의 모든 객체에 대해 도달성(Reachability) 조사 후, Unreachable한 Obejct들을 한 번에 삭제하는 방식으로 동작합니다.

 

 

🔥 Stop-The-World

GC가 동작하는 동안에는 GC를 실행하는 Thread를 제외한 모든 Thread의 동작이 멈추고, GC의 실행 이후에 중단되었던 작업들을 다시 시작합니다.

이를 Stop-The-World, 짧게는 STW라고 부릅니다.

 

Minor GC는 비교적 공간이 작은 Young Generation에서 발생하기 때문에 STW(Stop-The-World)가 비교적 짧은 방면,

Major GC의 경우 공간이 크기 때문에 STW가 비교적  길다는 특징을 가지고 있습니다.


GC의 종류

JVM에서 사용하는 GC 구현체를 크게 네 가지로 나눠볼 수 있습니다.

🔥 Serial GC

출처: https://www.perfmatrix.com/type-of-garbage-collector/

단일 스레드를 사용해 GC 작업을 하는 방식으로 가장 단순한 방법이지만, `STW(Stop-The-World)` 시간이 길다는 단점이 있습니다.

싱글 코어 환경이나 메모리가 적은 환경(임베디드 환경) 혹은 간단한 어플리케이션의 경우 Serial GC가 적합할 수 있습니다.

 

🔥 Parallel GC

출처: https://www.perfmatrix.com/type-of-garbage-collector/

 

Serial GC의 더 발전된 방법으로서, Java 8의 디폴트 GC입니다.

Serial 방식과 거의 유사하나, GC 작업(Minor, Major 모두)을 멀티 스레드로 수행합니다.

GC 중에는 동일하게 `Stop-The-World` 상태가 발생하지만, 작업을 병렬 처리하여 전체 GC 시간을 단축하는데에 초점을 맞추고 있습니다.

 

 

🔥 G1 GC

출처: https://www.perfmatrix.com/type-of-garbage-collector/

G1(Garbage First) GC는 기존의 방식과 다르게, Heap 메모리를 `크기가 같은 여러 Region`으로 나누고, 해당 Region을 Young/Old(Tenured) 영역으로 동적 할당하는 방식입니다.

  • Eden Region
    새로 생성된 객체들이 할당되는 영역으로 Young Generation의 일부
  • Survivor Region
    Eden Region에서 Minor GC에서 살아남은 객체들이 이동하는 영역으로 Young Generation의 일부
  • Tenured/Old Region
    Survivor Region에서 age threshold를 넘은 객체들이 저장되는 영역으로 Old Generation을 의미
  • Humongous Region
    Region 크기의 50%를 초과하는 큰 객체들을 저장하는 영역
  • Available Region
    아직 사용되지 않은 빈 영역을 의미하며, 필요에 따라 특정 영역으로 할당될 수 있음

G1 GC는 Java 9 이후 버전에서 디폴트 GC로 설정되어 있습니다.

 

G1 GC는 어플리케이션이 실행되는 중에 살아있는 객체를 계속 Mark하는데, 이 과정을 통해 Garbage 비율을 기록합니다.

G1 GC는 Heap 전체를 검사하는 대신,

필요에 따라 특정 Region(Garbage 비율이 높은 Region)을 먼저 수집(Collect)하는 방식으로 수행합니다.

 

 

🔥 Z GC

출처: https://d2.naver.com/helloworld/0128759

`Z GC`는 G1 GC와 같이 Region을 활용하는 방식이지만, Region의 크기를 동적으로 활용한다는 차이점을 가지고 있습니다. 

`Z GC`는 10ms 이내의 매우 낮은 중단 시간 - STW(Stop The World)를 목표로 합니다.

 

대규모 힙 메모리를 사용하며, 짧은 지연 시간이 필수인 경우 `Z GC` 사용을 권장합니다.

Java 11에서부터 실험적으로 도입되었으며, 15 버전부터 정식 기능을 지원하고 있습니다. 현재 `G1 GC`를 기본 GC로 제공하고 있기 때문에 `Z GC`를 옵션으로 설정하여 사용할 수 있습니다.


✅  참고 자료 & 링크

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

- JVM Garbage Collectors | Baeldung

- [10분 테코톡] 엘리의 GC

- Claude

- Chat GPT

 

 

 

 

 

728x90