GC 란?
업데이트:
GC란?
- Garbage Collection.
- GC란 JVM의 Heap 영역에서 사용하지 않는 객체를 삭제하는 작업을 말한다.
- GC는 다음 두가지 가설 하에 만들어졌다. (
weak generational hypothesis)- 대부분의 객체는 금방 접근 불가능 상태(unreachable)가 된다.
- 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.
GC 작업
- heap 내의 객체 중 가비지 (garbage)를 찾는다.
- 찾아낸 가비지를 삭제해서 힙의 메모리를 회수한다.
GC와 unreachable
- GC의 수거 대상은 heap 영역에 있는 unreachable한 객체. 즉, 어떠한 참조도 갖고 있지 않은 객체.
GC Root로부터 각각 어떤 객체를 참조하고 있는지 스캔해서unreachable한 객체를 수거.
GC Root
-
runtime data area는 다음과 같이
쓰레드가 차지하는 영역들과힙, 클래스 정보가 들어있는메서드영역 등으로 나눌 수 있다.
- 또한, 다음과 같이 힙에 있는 객체들에 대한 참조는 다음 4가지 중 하나이다.
- 힙 내의 다른 객체에 의한 참조
- Java 스택 프레임 내에 저장되는 지역변수와 파라미터에 의한 참조
- JNI에 의해 생성된 객체에 대한 네이티브 스택 내에서의 참조
- 메서드 영역의 static 변수에 의한 참조

- 이중에서
Java stack 내의 변수, JNI에 의해 생성된 객체에 대한 참조, 메서드 영역의 static 변수가 GC Root가 될 수 있다. - 그리고 GC Root에 의해 힙 내의 객체들에 대한 reachability가 결정된다.
- 만약, GC Root에 대한 참조가 없는 객체는 unreachable하다고 표현하며 GC의 수거 대상이 된다.
Stop the world
- gc를 수행시킬 때, gc를 수행하는 쓰레드를 제외한 모든 쓰레드가 정지되는 현상을 가리켜
Stop the world라고 한다. - stop the world는 gc 성능을 결정짓는 중요 요소이기 때문에 gc 튜닝을 한다라고 하면 대부분 stop the world 시간을 줄이는 것이 관건이 된다.
GC 동작과정
GC 세부 동작과정
- GC 동작과정을 알기전에 먼저 heap의 내부구조에 대해 알아야 한다.

- heap은 다음과 같이 크게 young gen, old gen으로 나뉜다.
- young gen은 다시 eden과 두개의 survivor로 나뉜다.
- heap 영역을 나누는 이유는 heap 전체를 탐색하여 메모리를 해제하는 full gc로 인한 성능상의 이슈를 최소화 시키기 위함이다.
- weak generational hypothesis 가설의 장점을 극대화 시키기 위함.

- 가장 최근에 생성된 객체들은 위와같이 eden 영역에 쌓인다.

- eden 영역이 가득차면, eden 영역을 대상으로 GC가 수행되며 이때 GC는
mark and sweep알고리즘을 사용해 가비지를 수거한다.- gc root를 스캔하면서 unreachable한 객체를 식별하는 과정을
mark, unreachable한 객체를 삭제하는 과정을sweep이라고 부른다. - reachable한 객체에는 mark bit를 1(true)로, unreachable한 객체에는 mark bit를 0(false)로 지정.

- 그리고 이때, eden 영역을 대상으로 수행된 GC를
minor gc라고 부른다.
- gc root를 스캔하면서 unreachable한 객체를 식별하는 과정을
- 이후, eden영역을 대상으로 minor gc가 수행되고 살아남은 객체들은 다음과 같이 survivor 영역으로 복제된다.

- 이때, 두개의 survivor 영역에 객체가 공존할 수 없다는 규칙이 있다.
- 즉, survivor 영역 중 하나는
반드시 비어있는 상태여야한다. - survivor 영역을 두개로 나누는 이유는 메모리 단편화 문제를 해결하기 위함이다.
- 즉, survivor 영역 중 하나는
- 위의 모든 과정들을 반복하면서 survivor 영역이 가득차면 young gen을 대상으로 minor gc가 수행된다.
- 이때, 살아남은 survivor 객체들은 다른 survivor 영역으로 복제되고, 가득찼던 survivor 영역은 모두 비운다.
- 이 과정을 수행하면서, 특정 임계점을 지날때동안 살아남은 survivor 내의 객체는 old gen으로 복제된다.
- 이 과정을 가리켜
promotion이라고 한다.
- 이 과정을 가리켜

- 위의 과정들이 반복되면서 old gen이 가득차면 old gen을 대상으로 gc를 수행하는데, 이를
major gc라고 한다.- major gc는 minor gc에 비해 스캔 대상이 많고 수행하는데 시간이 오래걸린다.
- major gc는 minor gc에 비해 수행 빈도가 적다.
트래픽이 무지많이 몰리는 이벤트가 예정되어있어, 트래픽이 많을때 young gen과 old gen 비율은 어떻게 하는 것이 좋을까?
- 트래픽이 많이 몰린다면 객체 생성도 그만큼 많이 될 것.
- 다만, 이러한 많은 객체들 중 수명이 짧은 객체들이 대부분일 것으로 예상됨.
- 이러한 상황에서, young gen이 차지하는 비율이 작다면 그만큼 minor gc의 빈도가 늘어날 것이고, minor gc에 의해 발생하는
stop the world의 빈도가 많아져 성능에 악영향을 줄 것임. - 그렇다면, minor gc에 의한 stop the world의 빈도 수를 줄이기 위해, young gen이 차지하는 비율을 높이면 어떻게 될까?
- minor gc에 의한 stop the world의 빈도수를 줄일 수 있겠지만, 해당 stop the world를 한번 수행할 때마다 그만큼 young gen에 객체가 많이 저장되기 때문에, 객체를 수거하는데 걸리는 시간은 이전보다 오래걸릴 것이다.
시간이 적게 걸리지만 많이 수행하느냐 vs 시간이 오래걸리지만 한번만 수행하느냐에 대한 차이라고 볼 수 있다.- 여기서는 stop the world 빈도를 줄이는게 맞는 선택이라고 생각한다.
- stop the world는 gc를 동작시키는 thread를 제외한 나머지 모든 thread를 all stop 하는 엄청난 비용을 소모하는 작업이기 때문이다.
- 하지만, old gen이 차지하는 비율도 상대적으로 작아져 old gen을 대상으로 하는
major gc의 빈도도 많아질 것이다. - 결국,
young gen을 대상으로 한 minor gc 의 빈도수 vs old gen을 대상으로한 major gc의 빈도 수사이의trade off가 발생하는 상황이라고 생각한다. - 다만, old gen은 young gen과 같이 eden이나 survivor space가 존재하는 게 아니기 때문에
메모리 단편화도 신경써야하고, 일반적으로 youn gen보다 더 많은 객체들을 관리하다 보니 major gc의 빈도가 높아지면 성능에 좋지 않은 역할을 줄 것은 분명하다. - 그렇기 때문에, old gen의 비율을 줄여가면서까지, minor gc의 빈도를 줄이기 위해 young gen의 비율을 높이는 것이
위험한 선택이 될 수 있을 것이라 생각한다. - 결론적으로… 오직 트래픽이 많을것이라는 가정만을 가지고 young gen과 old gen의 최적화된 비율을 수치화 하기는 어렵지 않을까? 라고 생각한다.
- 각 애플리케이션마다 저마다의 특징을 갖고있고, 애플리케이션을 동작시키는 환경도 제각각이어서, 여러가지 변수들이 존재할 것이기 때문이다.
- 그렇기 때문에 각 애플리케이션마다 대용량 트래픽에 대한 성능 테스팅이나 모니터링 과정을 통해 young gen과 old gen에 대한 최적의 비율을 찾는 것이 중요할 것이라 생각한다.
GC의 종류
Serial GC
- 가장 단순한 방식의 GC로 싱글 스레드로 GC를 동작시킨다.
- 싱글 스레드로 동작하여 느리고, 그만큼 STW 시간이 다른 GC에 비해 길다.
- Mark & Sweep & Compact 알고리즘을 사용한다.
- 보통 실무에서 사용하는 경우는 없다.
Parallel GC
- GC 작업을 여러 스레드로 병렬처리하여, STW 시간을 최소화 시키기 위한 GC이다.
- young gen에 대한 minor GC를 병렬처리 한다.
- Parallel Old GC라는 것도 있는데, 해당 GC는 old gen에서 수행되는 major GC도 병렬처리 한다.
- Parallel GC는 자바 8의 디폴트 GC 이다.
CMS GC(Concurrent Mark Sweep GC)
- STW로 인해 Java Application이 멈추는 현상을 줄이고자 만든 GC이다.
- 가비지 수거 후 compaction을 수행하지 않는다.
- reachable한 객체를 한번에 찾지 않고 4단계로 나눠서 찾는 방식을 사용한다.
- Initial Mark
- GC Root가 참조하는 객체만 마킹한다.
- 이 과정에서 stop-the-world 발생한다.
- Concurrent Mark
- 이후의 참조하는 객체를 따라가며, 지속적으로 마킹한다.
- stop-the-world 없이 이루어진다.
- Remark
- concurrent mark 과정에서 변경된 사항이 없는지 다시 한번 마킹하며 확정하는 과정이다.
- stop-the-world가 발생한다.
- Concurrent Sweep
- unrechable한 객체를 제거하는 과정이다.
- stop-the-world 없이 이루어진다.
- Initial Mark
G1GC
- Garbage first Garbage Collector 이다.
- Eden, Survivor, Old 영역을 고정된 크기로 고정된 위치에 나누는 것이 아니라, 전체 힙 메모리 영역을 리전이라는 일정한 크기로 나눠서 각 리전의 상태에 따라 그 리전의 역할이 동적으로 부여되는 방식으로 동작한다.
- 전체 힙에 대해서 탐색하지 않고 부분적으로 리젼 단위로 탐색하며, 각각의 리젼에만 GC가 발생해 빠르게 GC를 수행할 수 있다.
- 자바 9부터 디폴트 GC 이다.
댓글남기기