Garbage Collection이란?
자바의 메모리 관리 기법으로, 어플리케이션이 동적으로 할당했던 메모리 영역 중, 더이상 사용하지 않는 영역을 정리하는 것을 의미합니다. GC는 Heap 메모리에서 활동하며, JVM에서 GC의 스케줄링을 담당하며 개발자가 직접 관여하지 않아도 더이상 사용하지 않는 점유된 메모리를 제거해주는 역할을 수행합니다.
여기서 수행하는 기법으로 Stop the World가 있는데, Stop The World란 GC가 작동하는동안 GC관련 Thread를 제외한 모든 Thread는 멈추는 것을 의미합니다. 일반적으로 GC-튜닝은 이 시간을 최소화하는 것이지요.
GC의 위치
GC는 JVM메모리 구조에서 Exceution Enginge의 하위에 존재하고 있습니다.
GC Heap 영역
전통적인 heap영역은 위와 같은 구조이며, Eden, surivivor0, survivor1, old generation영역이 있습니다.
(Perm영역은 JAVA 7버전까지는 Heap Area에 존재하다가, 8버전부터 Native Method Stack에 편입되었습니다)
GC의 원리
아래의 두 가지 원리를 기반으로 물리적 공간을 Old영역과 Young영역으로 나눕니다.
1. 대부분의 객체는 금방 접근 불가능(Unreachable)한 상태가 된다.
ex) 메소드가 종료될때 지역변수에 할당한 객체들은 해당 메서드가 끝남과 동시에 더이상 접근 불가능한 상태가 됩니다.
2. 오래된 객체에서 젊은 객체로의 참조는 아주 적게 발생한다.
GC 알고리즘 종류
1. Reference Counting Algorithm
- Garbage 탐색에 초점을 맞춘 알고리즘으로, 각 객체마다 Reference Count를 관리하며, 이 카운트가 0이 되면 GC를 수행합니다. 단, 순환참조구조(가령, a가 b를 참조하고, b는 c를 참조하고, c가 다시 a를 참조하는 경우) 에서는 해당 count가 0이되지 않는 문제가 발생해서 Memory Leak이 발생할 수 있습니다.
2. Mark-and-Sweep Algorithm
- Reference Counting Algorithm의 단점을 극복하기 위해 나온 알고리즘으로, Root set에서 Reference를 추적하여 참조상황을 파악합니다.
1. Mark단계에서 Reference를 추적하여 참조상황을 파악 및 Garbage대상이 아닌 객체를 마킹하는 작업을 수행합니다.
2. 그 이후 Sweep 단계에서 마킹되지 않은 객체를 지우는 작업을 수행합니다.
위 단계를 마친 후 마킹 정보를 초기화합니다.
GC가 동작하고 있을경우, Mark작업과 어플리케이션 Thread충돌을 방지하기 위해 Heap사용이 제한됩니다.
단, Compaction 작업이 없어 비어있는 공간이 충분하지 않은 경우 OOM이 발생할 수 있다는 단점이 있습니다.
3. Mark-and-Compact Algorithm
Mark and Sweep알고리즘에서 발생하는 점유 메모리 분산 작업을 해결하기 위해 나온 알고리즘으로, Sweep 작업이후에 Compact(메모리 공간에서 사용하지 않는 빈 공간이 없도록, 메모리 분산 제거하는 작업) 작업이 추가되어 흩어져있는 메모리를 모아주는 작업을 진행합니다.
Compact작업으로 메모리 효율을 높일 수 있는 장점이 있습니다.
그러나 Compact작업과 그 이후 Reference를 업데이트 하는 작업으로 인해 Overhead가 발생할 수 있다고 합니다.
GC과정
GC는 각 영역을 활용하여 최적의 메모리 운영을 하게 됩니다.
1. 맨 처음 객체가 생성되면 Eden영역에 생성됩니다.
2. Minor GC가 발생하면, 미사용 객체의 제거와 함께 아직 사용되고 있는 객체는 Survivor0, Survivor1 영역으로 이동됩니다. 객체의 크기가 Survivor영역의 크기보다 클 경우, 바로 Old영역으로 가게 됩니다.
3. 운영 특성상 Survivor1과 Survivor 2영역은 둘 중 한 곳에만 객체가 존재하게끔 운영됩니다. 한 쪽의 Survivor가 꽉차면, 다른 Survivor영역으로 보내고 기존의 Survivor영역을 비우는 작업을 수행합니다.
둘 중 한 곳을 비우는 운영상의 이유는, 한 survivor영역에만 객체가 존재하도록 유지하여 객체의 이동을 최소화할 수 있기 때문입니다. 또한 두 survivor영역을 번갈아가며 사용하면, 객체가 한 번 이동할때마다 해당 영역이 완전히 비어있기 때문에 해당 survivor영역에 대해 사용할 준비가 되어있어서 새로운 객체할당에 대한 지연이 최소화됩니다 .
4. 1~3번을 반복하며 Survivor영역에서 계속 살아남은 객체들에게 일종 score가 누적되어 기준치 이상이되면 Old 영역으로 이동하게 됩니다. => 이를 Promotion으로 표현합니다.
5. Old 영역에서 살아남은 객체들이 일정 수준이상 쌓이게 되면, Full GC가 발생하게 됩니다. 이 과정에서 stop the world가 발생하게 됩니다.
GC종류
1. Serial GC
하나의 CPU로 Young Generation과 Old Generation을 연속적으로 처리하는 방식이며,
Mark-and-Compact 알고리즘을 사용합니다.
싱글 쓰레드 환경 및 Heap이 매우 작을 때 사용합니다.
하나의 쓰레드로 처리하니, Stop the world 시간이 매우 깁니다.
2. Parallel GC
자바 7~8버전에서 default로 설정되어잇는 GC입니다.
다른 CPU가 GC의 진행시간동안 대기 상태로 남아있는 것을 최소화한 알고리즘입니다.
여러스레드가 GC작업을 병렬로 처리하여 stop-the-world시간이 비교적 짧습니다.
3. Parallel Compacting GC
Old Generation 알고리즘 처리를 변경한 알고리즘입니다.
Parallel GC와의 차이점은, Compaction 작업 유무로 구분됩니다.
4. Concurrent Mark-Sweep GC
Application의 Thread와 GC Thread가 동시에 실행되어, Stop the world를 최소화하는 GC입니다.
다음과 같이 동작합니다.
1. Initital Mark
살아있는 객체를 찾아 마크 합니다. stop the world현상이 발생하지만, 모든 객체를 스캔하지 않고 살아있는 객체의 그래프를 탐색하기 때문에 짧은 시간 멈추게 됩니다.
2. Concurrent Mark
(1) 에서 찾은 객체를 따라가며 모두 살아있는지 확인합니다.
애플리케이션을 멈추지 않으며 GC와 애플리케이션의 리소스를 공유합니다.
3. Remark
(2) 에서 새로 생긴 객체나 참조가 끊긴 객체를 탐지합니다.
stop the world가 발생합니다.
4. Concurrent sweep
마크한 객체들을 sweep합니다.
stop-the-world가 발생하지 않습니다.
5. G1 GC
Java 9버전으로부터의 Default GC방식입니다.
큰 메모리에서 사용하기 적합한 GC로, 전체 Heap 영역을 체스판 처럼 Region이라는 영역으로 분할하며, 상황에 따라 역할이 동적으로 부여됩니다. 이렇게 작은 공간으로 Region을 나눈 이유는 메모리 조각화를 방지하기 위해서인데, 작은영역으로 구조를 나누어서 조각화를 최소화합니다.
Region에 정의하는 논리적 개념 종류는 다음과 같습니다.
1. Eden : 맨 처음 메모리 할당을 할 경우 들어가는 영역
2. Survivor: Minor GC 수행시 Eden영역에서 이동되는 객체들이 이동하는 영역
3. Old : Survivor에서 살아남은 객체들이 이동하는 영역
4. Available / Unused Region : 사용하지 않는 영역
5. Humongous Region : 객체의 크기가 region영역을 일정부분 채울 정도로 클 경우 해당 영역으로 이동합니다.
G1GC의 Full GC 동작 방식
G1 GC가 동작하는 방식은, 메모리가 많이 차있는 영역을 우선적으로 GC합니다. 영역을 나눠서 탐색하고, 영역별로 GC가 일어나지요.
비어있는 영역에만 새로운 객체가 들어가며, 쓰레기가 쌓여 꽉 찬 영역을 우선적으로 청소합니다. 이렇게 작은 영역의 객체를 계속 지우면서 stop the world현상은 많이 발생하나, 그만큼 멈추는 기간이 예측 가능하여 성능을 유지할 수 있습니다.
또한 Young Generation의 영역의 순서가 보장되지 않는데, 예컨대 Survivor1영역에 있는 객체가 Eden영역으로 가는것이 더 효율적이라 여겨지면, Eden영역으로 이동하게 됩니다.
단계별 동작방식은 다음과 같습니다.
1. Inital Mark : Old Region에 존재하는 객체들이 참조하는 Survivor Region을 찾음. 이 과정에서 stop-the-world 발생
2. Root Region Scan : Inital Mark에서 찾은 Survivor Region에 대한 GC대상 객체 스캔 작업을 진행한다.
3. Concurrent Marking : 전체 힙의 Region에 대해 스캔작업을 진행한다. GC대상이 없는 Region은 이후 단계를 처리하는데 제회된다.
4. Remark : stop-the-world를 진행하고, 최종적으로 GC대상에서 제외될 객체를 식별한다.
5. Cleanup : stop-the-world를 진행하고, 살아있는 객체가 가장 적은 Region에 대한 미사용 객체 제거를 수행한다. 이후 stop-the-world를 끝내고, GC과정에서 완전히 비워진 Region을 Availabe Region에 추가한다.
6. Copy:
GC대상 Region이었지만 clean-up과정에서 비워지지 않은 Region의 객체들을 새로운 Region에 복사해서 Compaction 작업을 수행한다.
G1GC의 Cycle
GC Cycle은 두가지 페이즈를 번갈아가며 GC작업을 진행합니다.
1. Young-Only : old객체를 새로운 공간으로 옮김
2. Space-Reclamation : 공간을 회수한다.
1. old gen의 점유율이 임계값을 넘어서면 young-only 페이즈로 전환된다.
2. concurrent start : 도달할 수 있는 객체에 마킹 작업을 진행한다.
3. remark : 마킹을 끝내고, 쓰레기 영역을 해지한다.
4. cleanup : space-reclamation 페이즈로 들어가야 할지 말지 판단
5. space-reclamation : 살아있는 객체를 적절한 곳으로 대피시킨다.
6. Z GC
ZPage라는 영역을 사용하여, G1 GC의 Region은 크기가 고정인데 비해, ZPage는 2mb배수로 동적으로 운영됩니다. 정지시간이 최대 10ms를 초과하지 않은 것을 목적으로 운영됩니다.
Heap크기가 증가하더라도 정지시간이 증가하지 않는것이 특징입니다.
참고하면 좋은 글 )
'Language > Java' 카테고리의 다른 글
StackTraceElement (0) | 2023.09.25 |
---|---|
객체지향 5대 원칙 요약 (0) | 2023.07.03 |
프로그래밍 패러다임과 객체 지향 프로그래밍 (0) | 2023.06.18 |
Custom Comparator (0) | 2023.01.23 |
compareTo (0) | 2023.01.23 |