이 글은 공부를 하면서 알게 된 내용들을 기록하는 글 입니다. 오류나 고쳐야 할 사항들이 있다면 지적 부탁드립니다!
✅ 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인지 여부를 판단합니다.
객체가 참조되고 있다면 Reachable로 구분되어 GC의 대상이 되지 않고,
객체가 참조되고 있지 않다면 Unreachable로 구분되어 GC의 대상이 됩니다.
✅ GC의 동작 과정
🔥 GC의 기본 전제
1️⃣ 대부분의 객체는 생성 후 금방 죽는다 (Young)
2️⃣ 오래 살아남은 객체는 계속 살아남을 것이다 (Old)
🔥 메모리 구조
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
단일 스레드를 사용해 GC 작업을 하는 방식으로 가장 단순한 방법이지만, `STW(Stop-The-World)` 시간이 길다는 단점이 있습니다.
싱글 코어 환경이나 메모리가 적은 환경(임베디드 환경) 혹은 간단한 어플리케이션의 경우 Serial GC가 적합할 수 있습니다.
🔥 Parallel GC
Serial GC의 더 발전된 방법으로서, Java 8의 디폴트 GC입니다.
Serial 방식과 거의 유사하나, GC 작업(Minor, Major 모두)을 멀티 스레드로 수행합니다.
GC 중에는 동일하게 `Stop-The-World` 상태가 발생하지만, 작업을 병렬 처리하여 전체 GC 시간을 단축하는데에 초점을 맞추고 있습니다.
🔥 G1 GC
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
`Z GC`는 G1 GC와 같이 Region을 활용하는 방식이지만, Region의 크기를 동적으로 활용한다는 차이점을 가지고 있습니다.
`Z GC`는 10ms 이내의 매우 낮은 중단 시간 - STW(Stop The World)를 목표로 합니다.
대규모 힙 메모리를 사용하며, 짧은 지연 시간이 필수인 경우 `Z GC` 사용을 권장합니다.
Java 11에서부터 실험적으로 도입되었으며, 15 버전부터 정식 기능을 지원하고 있습니다. 현재 `G1 GC`를 기본 GC로 제공하고 있기 때문에 `Z GC`를 옵션으로 설정하여 사용할 수 있습니다.
✅ 참고 자료 & 링크
- JVM Garbage Collectors | Baeldung
- Claude
- Chat GPT