GC(Garbage Collector)는 Heap 메모리에서 활동하며, JVM에서 GC의 스케줄링을 담당하여 개발자가 직접 관여하지 않아도 더이상 사용하지 않는 점유된 메모리를 제거해주는 역할을 담당한다.
GC는 ‘weak generational hypothesis’라는 가설을 기반으로 만들어졌다.
대부분의 객체는 금방 접근 불가능(unreachable)한 상태가 된다.
오래된 객체에서 젊은 객체로의 참조는 아주 적게 발생한다.
Permanent 영역은 Java 8부터 MetaSpace로 변경되었다.
Heap Area는 Eden, Survivor1, Survivor2, Old Generation으로 구분된다.
Java 9부터는 Heap memory의 Young이나 Old가 논리적으로 분할되어 있으며 동적으로 영역 사이즈를 변경하게 된다.
Eden, Survivor1, 2를 합쳐서 Young Generation이라고 칭한다.
Young Generation에서 발생하는 GC를 Minor GC, Old Generation에서 발생하는 GC를 Major GC라고 하며 Minor GC보다 속도가 느리다.
가비지 컬렉터에는 GC Root라는 것이 있다. GC Root들은 힙 외부에서 접근할 수 있는 변수나 오브젝트를 뜻한다.
GC Root는 말그대로 가비지 컬렉션의 Root라는 뜻이다.
GC Root에서 시작해 이 Root가 참조하는 모든 오브젝트, 또 그 오브젝트들이 참조하는 다른 오브젝트들을 탐색해 내려가며 마크(Mark)한다.
이 탐색해 내려가며 마크하는 것을 Mark단계라고 한다.
GC Root가 될 수 있는 것들은 다음과 같다.
Mark가 끝나면 가비지 컬렉터는 힙 내부를 전체를 돌면서 Mark되지 않은 메모리들을 해제(Reclaim)한다. 이 과정을 Sweep이라고 부른다.
Young Generation 영역에서 오래동안 살아남은 객체는 Old Generation 영역으로 옮겨지는데, 오래되었다고 판단되는 기준이 되는 값을 age라고 부른다.
각 객체는 Minor GC에서 살아남은 횟수를 기록하는 age bit 를 가지고 있으며, Minor GC가 발생할 때마다 age bit 값은 1씩 증가 하게되며, age bit 값이 MaxTenuringThreshold 라는 설정값을 초과하게 되는 경우 Old Generation 영역을 객체가 이동된다.
또는 Age bit가 MaxTenuringThreshold 초과하기 전이라도 Survivor 영역의 메모리가 부족할 경우에는 미리 Old Generation 으로 객체가 옮겨질 수도 있다.
이 Minor GC과정에서 살아남은 객체들에게 age값을 증가시켜주는 단계를 aging이라고 한다.
위에서 알 수 있듯이, sweep 과정을 끝낸 뒤 살아남은 객체들을 메모리 단편화를 방지하기 위해 Heap의 시작주소로 모으는 과정을 말한다.
객체가 생성이 되면 Eden 영역에 생성된다.
Eden 영역이 차게 되면 Minor GC가 발생하고 Mark and Sweep과 aging을 거쳐 살아남은 객체를 Survivor1 또는 Survivor2 영역으로 이동시킨다.
위 과정(1~2)을 반복하면서 Survivor 영역에서 계속 살아남는 객체들의 age값이 MaxTenuringThreshold 기준을 넘어가게 되면 Old Generation 영역으로 이동하게 된다.
Old Generation 영역에서 살아남았던 객체들이 일정 수준 쌓이게 되면 미사용된다고 식별된 객체들을 제거해주는 Full GC가 발생한다.
하나의 스레드로 Young 영역과 Old 영역을 연속적으로 처리하는 방식이다.
GC가 수행될 때 STW가 발생한다.
Client VM의 기본 컬렉터이며 현재는 거의 사용되지 않는다.
Mark-Sweep-Compact 알고리즘을 사용한다.
이 방식의 목표는 다른 CPU가 대기 상태로 남아 있는 것을 최소화하는 것이다.
Serial GC와 달리 Young 영역에서의 콜렉션을 병렬(Parallel)로 처리합니다. 많은 CPU 를 사용하기 때문에 GC의 부하를 줄이고 애플리케이션의 처리량을 증가시킬 수 있다.
Mark-Sweep-Compact 알고리즘을 사용한다.
Parallel GC와 비교하여 Old 영역에 mark-summary-compact 알고리즘을 사용한다.
mark-summary-compact 알고리즘은 다음과 같다.
이 방식은 low-latency collector로도 알려져 있으며, 힙 메모리 영역의 크기가 클 때 적합하다.
애플리케이션 스레드와 GC 스레드를 동시에 수행하여 stop-the-world 시간을 최소화하는데 목적이 있다.
Young 영역에 대한 GC는 Parallel GC와 동일히다. Old 영역의 GC는 다음 단계를 거친다.
Java 7에 정식으로 등장하여 Java 9부터 default가 된 GC 방법이다. (그 전까진 Parallel GC)
G1 GC는 큰 힙 메모리에서 짧은 GC 시간을 보장하는데 그 목적을 둔다.
Eden, Survivor, Old 영역이 존재하지만 고정된 크기로 고정된 위치에 존재하는 것이아니며, 전체 힙 메모리 영역을 Region 이라는 특정한 크기로 나눠서 각 Region의 상태에 따라 그 Region에 역할(Eden, Survivor, Old)이 동적으로 부여되는 상태다.
G1 GC에서는 그동안 봐왔던 Heap 영역에서 보지 못한 Humongous, Available/Unused 이 존재하며 두 Region에 대한 역할은 아래와 같다.
Humongous : Region 크기의 50%를 초과하는 큰 객체를 저장하기 위한 공간이며, 이 Region 에서는 GC 동작이 최적으로 동작하지 않는다.
Available/Unused : 아직 사용되지 않은 Region을 의미한다.
G1 GC에서 Young GC 를 수행할 때는 STW(Stop-The-World) 현상이 발생하며, STW 시간을 최대한 줄이기 위해 멀티스레드로 GC를 수행한다.
Young GC는 각 Region 중 GC 대상 객체가 가장 많은 Region(Eden 또는 Survivor 역할)에서 수행 되며, 이 Region 에서 살아남은 객체를 다른 Region(Survivor 역할) 으로 옮긴 후, 비워진 Region을 사용가능한 Region으로 돌리는 형태로 동작한다.
G1 GC에서 Full GC 가 수행될 때는 다음과 같은 과정을 겪는다.
JDK11에 early access로 포함되어 15에 Production Ready 상태인 gc 방법이다.
얼만큼의 Heap 공간을 갖고 있어도 STW 시간을 평균적으로 < 10ms
만큼 발생한다고 한다.
ZGC는 처럼 Region 구조를 가져가지만 G1GC와는 다른 메모리 구조를 가져간다.
ZGC에는 속도와 안정성을 위해 Colored pointers
와 Load barriers
라는 주요한 알고리즘 2가지가 들어가 있다
Colored pointers는 객체를 가리키는 변수의 포인터에서 64bit라는 메모리를 활용하여 Mark를 진행하여 객체의 상태값을 저장하여 사용하는 방식이다
위에 이미지 처럼 42bit는 객체를 가리키는 주소값으로 사용하였고, 나머지 22bit중 4bit를 4가지 Finalizable, Remapped, Marked 1, Marked 0로 나눠서 표시했다.
이 때문에 ZGC는 64bit 운영체제에서만 작동한다.
Load barriers은 Thread에서 참조 객체를 Load할때 실행되는 코드다
ZGC는 재배치에 대해서 STW없이 동시적으로 재배치를 실행하기 때문에 참조를 수정해야 하는 일이 일어나게 된다
이때 Load barriers가 RemapMark와 RelocationSet을 확인하며 참조와 Mark를 업데이트하고 올바른 참조값으로 인도해준다
ZGC는 총 3번의 Pause만이 일어난다
Pause
Mark Start : ZGC의 Root에서 가리키는 객체 Mark 표시 (Live Object)Pause
Mark End : 새롭게 들어온 객체들의 대해서 Mark를 표시한다Pause
Relocate Start : 모든 루트참조의 재배치를 진행하고 업데이트한다Shenandoah GC는 아직 분석 및 참고 자료가 부족하여 차후에 보충할 예정이다.
참조