런타임 시에 JVM이 동작하며 Minor GC와 Major GC가 동작하여 쓰레기를 수집하는 것은 알았다.
그렇다면 쓰레기 수집은 GC에서 실제로 어떻게 일어날까?
그 전에 알아야하는 JVM의 'stop-the-world'
DIO의 'the world'가 아니다.
stop the world
stop-the-world는 GC를 실행하기 위하여 JVM이 모든 애플리케이션의 실행을 멈추는 것이다.
stop-the-world가 발생하면 GC를 실행하는 GC 쓰레드 이외에 모든 쓰레드는 말 그대로 'stop'한다. GC 작업이 완료되면 'stop'은 풀리고, 어떤 GC 알고리즘을 사용하더라도 stop-the-world는 일어난다.
이러한 stop-the-world의 시간을 줄이는 것이 바로 GC 튜닝이다.
GC가 존재할 수 있는 이유
메모리의 동적 할당은 두가지 방법으로 해제가 가능하다.
따라서 GC가 쓰레기 수집(객체 지우기) 작업을 하는데 이는 두 가지의 전제 조건하에서 동작을 시작했다.
1. 대부분의 객체는 금방 접근 불가능 상태(unreachable)가 된다.
2. 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.
이 가설에 따라 JVM의 메모리 공간은 매우 많은 객체가 생성되는 Young Generation과
Minor GC가 발생하고도 살아남은 객체가 모이는 Old로 나뉘었고, 각 Minor GC와 Major GC가 발생한다.
Old영역이 Young Generation을 참조하는 경우
Young Generation 일명 Young 영역은 수많은 객체가 생성되고 그만큼 GC가 자주 발생한다.
그런데 만약 Old 영역의 객체가 Young 영역의 객체를 참조해야 한다면, GC의 대상이 되지 말아야할 것이다.
그 구분을 어떻게 하는가?
Young Generation GC 동작
앞서 봤듯이 Young 영역은 Eden과 Survivor1/2로 구성되어 총 3개의 영역을 가진다.
사진
이 사진과 같이 쓰이지 않는 객체는 Eden과 Survivor에서 Minor GC에 의해 처리되고, 살아남은 객체는 각 Eden -> Survivor -> Old로의 이동을 거치게 된다.
이러한 GC 과정에는 TLABs와 bump-the-pointer 기술이 사용된다는데 아직 그정도까진 도달하지 않아도 될 것 같다.
Old 영역의 GC 동작
Old 영역은 Young과 달리 기본적으로 데이터가 가득 차면 Major GC를 실행한다.
JDK 7을 기준으로 5가지의 Major GC 방식이 존재하는데
그런데 이 중에서 운영 서버에서 Serial GC는 절대 사용하면 안된다고 한다.
단일 코어 방식을 위해 만든 방식이라 프로세스 파워의 낭비가 심하다고 한다.
Serial GC는 적은 메모리, 적은 코어에 적합한 방식
Young 영역은 bump-the-pointer와 TALBs 방식을 사용
Old 영역은 Mark & Sweep Compact 알고리즘을 사용한다.
1. Old 영역의 살아있는 객체를 Mark
2. Heap의 앞 부분부터 확인하여 살아 있는 것을 남기는 Sweep
3. 각 객체들이 연속적으로 힙을 채워 힙에 객체가 존재하는 부분과 존재하지 않는 부분으로 나누는 Compaction
Serial GC와 기본적인 알고리즘은 같으나, Parallel GC는 이름답게 멀티 쓰레드를 사용한다.
따라서 Parallel GC는 메모리 크기가 클 때와 다중 코어에서 유리하다.
이는 CMS GC를 사용해서 얻는 latency(지연) 단축에 대한 것보다 throughput(성능)이 더 중요할 때 사용 된다.
Parallel GC는 Old 영역의 GC 알고리즘이 Mark-Summary-Compaction을 거친다.
Summary 단계는 Sweep과 달리 GC를 수행한 영역에 대한 객체 식별을 거친다.
사진과 같이 CMS GC는 일반적인 GC보다 복잡하다.
각 단계가 각 한가지의 일만을 실행하기에 'stop-the-world'로 인해 JVM의 실행이 멈추는 시간은 매우 짧고, 응답 속도가 매우 중요한 작업에서 많이 사용한다.
그러나 CMS GC는 다음과 같은 단점을 가지는데
이 GC 방식은 조금 특이한데 Young Generation과 Old 영역이 존재하나, 고정된 크기와 위치로 존재하지 않는다.
G1 GC는 CMS GC를 대체하기 위해서 만들어진 GC로
Heap Space를 Region이라는 일정 크기로 나누어 각 Region에 Young, Old를 동적으로 부여한다.
JVM의 Heap은 2048개의 Region으로 나뉘어 질 수 있으며, 각 Region의 크기는 1 ~ 32MB까지 지정 가능하다.
JVM이 가질 수 있는 Heap 크기를 조절 가능
Max Heap Size는 64Bit 8GB 메모리 기준 최대 1/4
2048MB까지 지정 가능, 2048개의 1MB Region으로 나눌 수 있음
G1 GC는 다음과 같은 특징들을 가진다.
G1 GC의 Major(Full) GC 과정
이러한 특징들로 인해 G1 GC는 JDK8부터 기본 GC 알고리즘으로 사용되고 있다.
Java11부터는 Z gc가 시험 도입 되었음