해당 포스트의 작성 목적
예전 GC포스팅을 보면서 잠깐 훑었는데 G1 GC에 대한 내용이 너무없었다. 그리고 좀더 읽기 쉽게, 그리고 좀 더 이해가 갈수 있도록 내용을 수정하고자 한다.
이전 게시물 : https://hpotter1993.tistory.com/32
GC = >GarbageCollector
https://asfirstalways.tistory.com/159
GC는 기본적으로 GC Root의대상에서 Reachable한 객체와 UnReachable한 객체를 나누고, UnReachable한 객체를 소거한다.
해당 객체에 접근할 수있는 수단이 있다면 Reachable이다. (주소값을 참조하는 변수가 있으면 됨)
- GCRoot가 될수 있는 대상
- JVM Stack영역의 변수들
- Static 변수
- JNI로 생성된 객체
GC는 각 객체가 old한지, young한지 판별하는 기준으로 Aging이라는 개념을 쓴다.
해당객체가 살아남은 GC의 횟수를 Age라 하며, 대부분 8→9 시점에 young→old영역으로 이동하며, 이를 승격(Promotion)이라 한다.(명시적으로 지정도 가능하다)
GC의 전제조건
weak generational hypothesis
- 대부분의 객체는 금방 접근 불가능한 상태가 된다.
- 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.
MinorGC
새로 생성된 대부분의 객체는 Eden영역에 위치한다.
Eden영역에서 GC가 한 번 발생한 후 살아남은 개체는 Survivor영역중 한 곳으로 이동된다. (GC가 발생할 때마다 랜덤으로 가지않고, 하나의 Survivor 영역이 다 찰 때까지 한 Survivor영역으로 이동한다.)
하나의 Survivor 영역이 가득차면 그중 살아남은 객체를 다른 Survivor 객체로 이동한다.
그리고 가득 찬 Survivor 영역은 아무 데이터도 없는 상태가 된다.
이 과정을 반복하며 계속 살아남는 객체는 일정시간 참조 되고 있다는 뜻으로, Old영역으로 이동한다.
MajorGC
Old영역에 있는 모든 객체들을 검사, 참조되지 않은 객체들을 한번에 삭제한다. 시간이 오래 걸리고 실행 중 모든 프로세스가 정지된다. 이를 stop the world라고 하며, GC튜닝은 stop the world 시간을 줄이는 것을 목표로 한다.
GC의 방법 5가지
4가지는 방식이 앞서 설명한 것과 비슷하고, 1가지는 아예 새로운 개념으로 GC를 수행한다.
- Serrial GC
- 하나의 CPU로 Young과 Old영역을 연속적으로 처리한다.
- 컬랙션이 수행 될때 애플리케이션이 정지된다.
- Old영역에서 쓰지않는 객체를 표시(Mark)하고, heap의 앞부분부터 확인, 살아있는 것만 남긴다(Sweep), 이후 각 객체들이 연속되게 쌓이도록 힙의 가장 앞부분부터 채워서 객체가 존재하는 부분과 객체가 없는 부분으로 나눈다.(Compaction)
- Mark Sweep Compact알고리즘을 사용한다.
- Parallel GC
- SerialGC와 기본적인 알고리즘은 같지만, GC를 처리하는 스레드가 여러개있다.
- Young영역에 대해서 멀티스레드로 병렬처리를 진행한다. -> SerialGC보다 더 빠르다.
- ParallelOld GC
- ParallelGC와 Old영역 GC를 수행하는 알고리즘이 다르다.
- 기존 Mark-Sweep-Compaction -> Mark-Summary-Compaction단계를 거친다.
- Summary는 앞서 GC를 수행한 영역에 대해서도 별도로 살아있는 객체를 식별한다는 점에서 Sweep과 다르며, 좀더 복잡하고 빠르다.
- CMS GC(Concurrent Mark Start GC)
- CMS GC의 방법
- 짧은 대기 시간(STW)으로 살아있는 객체를 찾는다.(Initial Mark)
- 서버 수행 시 살아있는 객체에 표시를 한다.(Concurrent Mark)
- 표시 도중에 변경된 객체에 대해 다시 표시한다.(Remark)
- 표시된 쓰레기를 정리한다. 다른 방식보다 메모리와 CPU를 더 사용한다.(Concurrent Sweep)
- 왜 CMS의 속도가 빠른걸까?
- 기존 GC는 용량이 차면 GC를 수행했다. GC가 수행되는 시점에 객체들의 Reachable을 판단하고, 참조가 없는 객체들을 지웠다. 그러나 CMS 방식은 이름에서 알수있듯이, GC대상여부에 대한 판단(Mark)를 Concurrent하게 진행한다. 처음 시작부터(Initial Mark) 지정하고, 이후에는 별도의 쓰레드로 stw없이 서버수행중에 살아있는 객체들을 Mark한다. 이후 Remark 에서 추가되고, 삭제된 객체들을 다시 표시한다. 즉 이미 초기부터 mark를 계속해오기 때문에 stw 시간자체도 줄어들고, 짧은시간으로 나누어 진행하게되어 더 빨라지는것이다.
- stop-the-world가 매우 짧다. compaction을 제공하지 않기 때문에, 개발자가 직접 해줘야 한다.
- CMS GC의 방법
- G1 GC(Garbage First GC)
- G1 GC는 바둑판 모양으로 구성되어 있으며, 같은 크기로 나눈 약 2000개의 구역을(Region이라 부른다) 사용한다. 이렇게 나뉜 영역들을 일부는 Young영역, 일부는 Old영역으로 지정한다.
- CMS GC 처럼 객체의 Reachable여부를 쪼개어 진행한다. (Initail - Concurrent - Remark)
- G1 GC의 방법
- Major GC는 Minor GC에 의존적이다. Minor GC가 끝낫을때 InitialMark를 진행한다.
- 이후 서버 수행을 하며 Concurrent Mark를 진행한다.
- Remark시 여러개의 Region중 Garbage로 꽉찬 영역들을 알수있다. 이때 해당 Region을 먼저 회수한다. ( G1 GC로 이름붙은 이유)
- 이후 살아남은 객체들은 Evacuation을 진행, 새로 지정된 Survivor영역으로 이동한다.(Copy&Paste) 이 과정에서 자연스럽게 여러 Region에 퍼진 객체들이 한 Region으로 모인다. 즉 Compaction이 된다.
- Garbage영역을 먼저 회수하므로, young영역의 객체들의 old 조기승격을 방지 할수 있다.
- Heap영역 전체에 대해서 compaction 을 진행하는 타 GC(CMS제외)와 달리, Region에 대해서만(영역이 더 작다!) Compaction이 진행되어 성능상 이점이 있다.
- 단 G1 GC는 많은 메모리를 사용하므로, 최소 6GB이상 서버에서 사용해야 하며, 메모리가 적은 서버에서 사용시 메모리부족으로 계속 Full GC가 일어날수있다.
포스트를 작성하며 알게된 점
- GC를 공부하면서, 왜 Static 변수들을 남발하면 안되는지 알게되었다. Static변수는 프로그램이 끝날때까지 살아있으므로, 힙메모리의 참조가 끊기지 않아 GC대상이 안되기 때문이었다
- 이 포스트에서 기술한 G1 GC의 방식은 내가 이해한 내용을 정리한 내용이며, 라이프사이클로 설명하는 부분도 있는데, 여기는 아직 이해가 잘되지 않는다. 추후에 이해가 되면 또 업데이트하자.
- CMS, G1 GC의 방식이 왜 빠른건지, 어째서 더 좋은건지 이전에는 잘 이해가 되지 않았다. 그러나 이번 포스팅으로 GC STW를 최대한 줄이고, 쪼개어 진행하는 방향으로 발전된 것을 이해했다.
Reference
'프로그래밍 > Java' 카테고리의 다른 글
Spring Data JPA 간략 사용법! (1) | 2022.09.23 |
---|---|
Builder패턴이란? (0) | 2022.08.18 |
Dynamic Proxy 직접 구현해보기-2 (0) | 2021.08.10 |
Dynamic Proxy직접 구현해보기-1 (0) | 2021.08.10 |
Comparator의 동작방식 (0) | 2021.07.27 |