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 이다.
댓글남기기