가비지 컬렉션
JVM 이 어떻게 메모리 관리를 하는지 알아보기
자바에서 힙 영역이 설계될 초기엔 저장되는 객체는 대부분 일회성이고, 메모리에 오랫동안 존재하는 경우는 드물다는 전제하에 설계 되었다.
이로인해 객체의 라이프 사이클에 맞춰 물리적힌 힙 영역을 마치 청년과 노년을 구분하듯 세대별로 나눈 세대별 컬렉션 이론이 적용 되었다.
GC 동작 방식
GC 로 인해 프로그래머는 더 이상 메모리 영역에 대한 깊은 관심을 갖지 않아도 된다. 이는 C 언어 이후 장족의 발전이기 때문에 굉장히 효율적일 것이라 생각하겠지만, 이 전지전능한 기능에도 단점이 존재한다.
Stop-The-World
메모리 영역에서 참조되지 않는 객체를 탐색하여 회수하는 작업을 하기 위해 JVM 은 어플리케이션의 실행을 멈추어 GC 에게 우선권을 준다.
이로인해 GC 가 동작하는 동안 GC 와 관련된 스레드를 제외한 나머지 스레드는 멈추게 되어 오버헤드가 발생하고, 모든 스레들의 작업이 중단되면 어플리케이션도 같이 멈추게된다.
효율적인 기능이지만 이런 단점이 존재하기 때문에 GC를 더 최적화 하기 위해 튜닝을 선택하게 된다.
Mark and Sweep
Mark: 사용 중인 메모리와 사용하지 않는 메모리를 식별하는 곳이다.
Sweep: 이 단계에서는 "표시" 단계에서 식별된 개체를 제거한다.
Minor GC
Young Generation 에 새로운 객체를 지속적으로 생성하며 관리 되는 메모리가 꽉 차게 되어 더 이상 새로운 객체를 생성할 수 없을 때 Minor GC 가 동작한다.
Young Generation 에 저장되어 있는 객체를 모두 검사하여 아직 참조 되고 있는 것과 참조 되지 않는 상태를 모두 마킹하여 참조 중인 객체는 Old Generation 영역으로 복사하고, 참조 되지 않는다면 자원을 회수하게 된다.
Minor GC 가 동작하는 소요 시간은 보통 1초 이내로 완료 되기 때문에 Stop-The-World 같은 오버헤드가 발생하지 않는다.

이 내용을 조금 더 깊게 살펴보면, 더 효율적인 GC 를 위해 Young Generation 영역을 3가지 영역으로 분리하여 관리한다.
Eden: 앞서 계속 설명 되었던 인스턴스가 생성될 때 해당 영역에서 관리한다.
Survivor 0 / Survivor 1: Eden 영역에서 Minor GC 가 동작한 후 한 번 이상 살아남는 객체를 Survivor 0 또는 1 영역으로 이동하며, 이 때 두 영역 중 하나는 무조건 비어있어야 한다.
만약, Survivor 영역 중 한 영역이라도 가득차면 마치 고가용성을 구성하듯 다른 Survivor 영역으로 이동한다.
Survivor 영역에서 객체가 살아남게 되면
age값이 1씩 증가하게 되고, Old Generation 으로 이동하기 위한 임계값은 기본적으로 31이다.Minor GC 가 동작하고 Survivor 영역에서 여러번 살아남는다면 Old Generation 영역으로 이동하게 된다.
Major GC
Old Generation 영역에서 메모리가 부족할 때 때 발생한다.
Old 영역에 있는 모든 객체들을 검사하여 참조 되지 않은 객체들을 한 번에 삭제하는 과정이 발생하는데, Young Generation 에 비해 큰 메모리 공간을 차지하고 있어 객체 제거에 많은 시간이 걸린다.
또한, 스택 프레임이 소멸될 때 힙 영역에 있는 객체를 참조하는 변수(참조자)가 사라지면서 GC에 의해 제거될 수 있지만 다른 공간에서 다시 참조하여 사용할 수 있는 등 의존성이 깊어지는 것도 탐색 해야 하기 때문에 시간이 오래 걸린다.
평균적으로 수초 이내에 중료 되며 앞서 보았던 Stop-The-World 가 여기서 발생하게 된다.
Major GC 는 특히 어플리케이션에 성능 저하를 줄 수 있기 때문에 메모리 관리와 GC 튜닝을 고려해야하며, 굉장히 단순하게는 서버를 일시적으로 재기동하여 해결하기도 한다.
GC 구현체
JVM 에는 총 4 종류의 GC 구현체들이 존재한다.
Serial Garbage Collector
Parallel Garbage Collector
G1 Garbage Collector
Z Garbage Collector
Serial GC
기본적으로 단일 스레드에서 동작하기 때문에 가장 간단하게 구현된 GC 이다.
다중 스레드를 사용하는 어플리케이션에서 사용하지 말 것을 권장한다.
단일 스레드로 동작하기 때문에 Stop-The-World 시간이 길다.
Parallel GC
Java 5 부터 Java 8 까지 JVM 의 기본 GC 이며, Serial GC 와 동작 방식은 같지만 더 나은 힙 공간을 관리하기 위해 Minor GC 는 멀티 쓰레드로 수행 되고 Major GC 는 싱글 쓰레드로 수행된다.
Serial GC 에 비해 Stop-The-World 시간이 감소 됨
G1(Garbage First) GC
Java 7 버전에서 메모리 공간이 큰 멀티 쓰레드에서 실행되는 어플리케이션을 위해 설계 되었으며, Java 9 버전의 기본 GC 이다.
다른 GC 와 달리 Region 이라는 새로운 개념을 이용하여 힙을 동일한 크기의 힙 영역 집합으로 분할한다.
Eden, Survivor, Old 등 세대별 컬렉션 이론에 고정적이지 않고 동적으로 힙 영역을 관리한다.
모든 객체의 참조 상태를 일일이 확인하지 않고 메모리가 많이 차있는 영역을 우선적으로 GC가 동작한다.
Z GC
Java 11 버전에서 대량의 메모리(8MB에서 16TB 크기의 힙)를 low latency로 처리하기 위해 설계 되어 Linux OS의 실험용으로 등장하였고, Java 15 버전부터 기본 GC 로 등록 되었다.
ZGC 는 Stop-The-World 가 10ms 가 넘지 않고, 비용이 많이 드는 작업을 동시에 수행하기 때문에 대기 시간이 짧은 어플리케이션에서 유리하다.
힙 크기가 증가 하더라도 Stop-The-World 는 10ms 를 넘지 않음
쓰레드가 실행중일 때 Color Pointer 를 적용하여 힙 사용량을 추적하는데, G1 의 Region 처럼 Zpage 라는 영역을 사용하며 고정적인 크기가 아닌 2MB 배수로 동적으로 운영하는 것이 특징이다.
참고 자료
Last updated
