Garbage Collection이란?
- GC는 메모리 관리 기법 중 하나로, 동적으로 할당했던 메모리 영역 중 필요 없게 된 영역을 해제하는 기능이다.
- 보통 프로그램을 개발하면 더 이상 사용하지 않은 메모리가 발생한다.
- C언어에서는 이를
free() 로 참조를 해제하지만, Java는 GC가 알아서 이를 해결해준다
- 가장 간단하게 구현할 수 있는 방법은 JVM의 Heap 메모리에서 모든 객체를 돌면서, 더 이상 사용되지 않는 객체인지 판단하고 해제하는것이다.
- GC는 모든 Garbage를 수집해야하는 동시에, 살아있는 객체는 절대로 수집해서는 안됩니다.
- 만약, 수집한다면 Segmentaion Fault가 발생
Root Space

- 스택 변수, 전역 변수 등 heap 영역 참조를 담은 변수
- 노란색 영역
GC 알고리즘
Reference Counting

- Heap 영역에 선언된 객체들이 각각 reference count 라는 별도 숫자를 가지고 있다.
- reference count는 몇가지 방법으로 해당 객체에 접근할 수 있는지를 뜻한다.
- 해당 객체에 접근할 수 있는 방법이 없다 = reference count : 0 → GC 대상
- 해당 방식에는 순환 참조 문제가 있다.
- 그림 속 Root Space에서 모든 Heap Space의 참조를 끊는다고 가정하자.
- 그러면 노란색 고리 안의 객체는 서로가 서로를 참조하고 있기 때문에 reference count가 1로 유지된다.
- 결국 사용하지 않는 메모리 영역이 해제되지 못하고 메모리 누수가 발생하는 것이다.
Mark And Sweep

- Mark And Sweep은 Reference Counting의 순환 참조 문제를 해결 가능
- Root Space부터 해당 객체에 접근 가능한지, 아닌지를 메모리 해제의 기준으로 삼는다.
- Root Space부터 연결된 객체를 찾아내고(Mark) 연결이 끊어진 객체는 지운다.(Sweep)
- Root Space부터 연결된 객체 Reachable, 연결되지 않은 객체 Unreachable
- 오른쪽 그림이 메모리 파편화를 방지하는 Compaction 과정이다.
- Sweep 과정에서 삭제된 메모리의 Fragment들을 채워주는 역할을 한다.
- Compaction이 필수 과정은 아니다.
- GC를 실행하기 위해서는 GC를 실행하는 Thread를 제외한 나머지 Thread는 모두 작업을 멈춥니다.
- 이후 GC 작업이 끝난뒤에 나머지 Thread가 다시 동작하게 됩니다.
- 이때 발생하는 시간이 Stop the world
- Stop the world시간을 최적화 하는것이 GC 튜닝의 목표입니다.
Heap

- JVM의 Heap영역은 처음 설계될 때 2가지를 전제(Weak Generational Hypothesis)로 설계
- 대부분의 객체는 금방 접근 불가능한 상태(Unreachable)가 된다.
- 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다.
- 즉, 객체는 대부분 일회성이며, 메모리에 오랫동안 남아있는 경우는 드물다.
- 따라서 객체의 생존 기간에 따라 Heap 영역을 Young, Old 총 2가지 영역으로 나누었다.
- Young Generation 안에서 최대한 메모리를 해제하도록 설계
- 초기에는 Perm 영역이 존재하였지만 Java8부터 제거되었다.
- Young 영역(Young Generation)
- 새롭게 생성된 객체가 할당(Allocation)되는 영역
- 대부분의 객체가 금방 Unreachable 상태가 되기 때문에, 많은 객체가 Young 영역에 생성되었다가 사라진다.
- Young 영역에 대한 가비지 컬렉션(Garbage Collection)을 Minor GC라고 부른다.
- Old 영역(Old Generation)
- Young영역에서 Reachable 상태를 유지하여 살아남은 객체가 복사되는 영역
- Young 영역보다 크게 할당되며, 영역의 크기가 큰 만큼 가비지는 적게 발생한다.
- Old 영역에 대한 가비지 컬렉션(Garbage Collection)을 Major GC라고 부른다.
💡 Old 영역이 Young 영역보다 크게 할당되는 이유는 Young 영역의 수명이 짧은 객체들은 큰 공간을 필요로 하지 않으며 큰 객체들은 Young 영역이 아니라 바로 Old 영역에 할당되기 때문
Young generation
- Eden, Survivor0, Survivor1 영역으로 나뉜다.
- Eden 영역: 새로 생성된 객체가 할당(Allocation)되는 영역
- Survivor 영역: 최소 1번의 GC 이상 살아남은 객체가 존재하는 영역
- Eden은 새롭게 생성된 객체들이 할당되는 영역
- Eden영역이 꽉차면 minor gc가 발생
- 사용하지 않는 메모리는 해제되고 해당 영역에 존재하는 객체는 Survivor 영역으로 이동
-
새로 생성된 객체가 Eden 영역에 할당된다.
-
객체가 계속 생성되어 Eden 영역이 꽉차게 되고 Minor GC가 실행된다.
-
Eden 영역에서 사용되지 않는 객체의 메모리가 해제된다.
-
Eden 영역에서 살아남은 객체는 1개의 Survivor 영역으로 이동된다.
-
1~2번의 과정이 반복되다가 Survivor 영역이 가득 차게 되면 Survivor 영역의 살아남은 객체를 다른 Survivor 영역으로 이동시킨다.(1개의 Survivor 영역은 반드시 빈 상태가 된다.)
-
이러한 과정을 반복하여 계속해서 살아남은 객체는 Old 영역으로 이동(Promotion)된다.
Age bit
- GC는 객체가 얼마나 오래 살아남아있는지를 알기 위해서 살아남을 때 마다 Age bit를 1씩 증가하게 됩니다.
- Eden → Suvivor로 갈때 증가합니다.
- Suvivor → Suvivor로 이동할 때도 증가를 합니다.
- 기본설정으로는 Age-bit이 15가 될 경우에는 Old Generation으로 이동하게 됩니다.
- 또한, 각 영역으로 이동하는것을 Promotion이라고 부릅니다
Minor GC
- Minor GC는 실행시점이, Eden이 꽉찰경우 실행되며 이때 Young generation 영역을 모두 Mark And Sweep 합니다.
- 속도가 빨라 Application에 크게 영향을 주지 않습니다.
- 보통 0.5초에서 1초 사이에 끝난다.
Old Generation
- 오래 살아남은 객체는 Old Generation으로 이동하게 됩니다.
- 이 영역에서 객체의 메모리를 해제하는 것을 Major GC 라고 합니다.
Major GC
- Major GC는 객체들이 계속 Promotion되어 Old 영역의 메모리가 부족해지면 발생
- Old 영역은 Young 영역보다 크며 Young 영역을 참조할 수도 있다.
- Major GC는 일반적으로 Minor GC보다 시간이 오래걸리며, 10배 이상의 시간을 사용한다.
- 참고로, Major와 Minor를 모두 돌리는 것을 Full GC라고 합니다
Serial GC

- 말 그대로 Serial 하게 하나의 Thread가 GC작업을 처리하는것을 말합니다.
- Serial GC의 Young 영역은 앞서 설명한 알고리즘(Mark Sweep)대로 수행된다.
- 하지만 Old 영역에서는 Mark Sweep Compact 알고리즘이 사용된다.
- Compact는 Heap 영역을 정리하기 위한 단계로 유요한 객체들이 연속되게 쌓이도록 힙의 가장 앞 부분부터 채워서 객체가 존재하는 부분과 객체가 존재하지 않는 부분으로 나누는 것이다.
- Core가 1개인 경우에 사용을 하는경우가 있지만, 현재는 거의 쓰이지 않는다.
Parallel GC

- 이 또한, Mark And Sweep And Compact 방식으로 동작하는 GC입니다.
- 여러개의 Thread로 GC가 동작한다는 장점이 있습니다.
- 설정에 따라서 Major와 Minor에 대해서 Single Thread로 돌릴지 결정을 할 수 있습니다.
- 일반적인 Parallel GC는 minor gc에 대해서만 멀티 스레딩을 수행하고, major gc는 싱글 스레딩으로 수행한다.
- 멀티 코어 환경에서 애플리케이션 처리 속도를 향상시키기 위해 사용된다.
- CPU의 Core와 메모리가 충분할때 유리한 방법으로 Throughput GC라고도 불린다.
Parallel Old GC
- Parallel Old GC는 Parallel GC의 업그레이드된 버전이다.
- major gc도 멀티 스레딩으로 수행하고 기존 Mark Sweep Compation의 개선 버전인 Mark Summary Compaction을 사용한다.
- Java 8의 디폴트 버전이다.
Concurrent Mark-Sweep GC (CMS)

- CMS GC는 애플리케이션의 지연 시간을 최소화 하기 위해 고안되었다.
- 가비지 수집 작업을 애플리케이션 스레드와 동시에 수행하여, Stop The World 시간을 최소화
- 응답이 느려질 순 있지만 응답이 멈추지는 않게 된다.
- 하지만 메모리와 CPU를 많이 사용하고, Mark And Sweep 과정 이후 메모리 파편화를 해결하는 Compaction이 기본적으로 제공되지 않는다는 단점이 있다.
- 이 때문에 시스템이 장기적으로 운영되다가 조각난 메모리들이 많아 Compaction 단계가 수행되면 오히려 Stop The World 시간이 길어지는 문제가 발생할 수 있다.
- CMS GC는 Java 9 버전부터 deprecated되었고 Java 14 버전부터는 사용이 중단되었다.
- Initial Mark: 클래스 로더에서 가장 가까운 객체 중 살아 있는 객체만 찾는다.
- Concurrent Mark: 위에서 살아 있다고 확인한 객체에서 참조되어 있는 객체를 확인한다.
- Remark: 위 단계에서 새로 추가되거나 참조가 끊긴 객체를 확인한다.
- Concurrent Sweep: 쓰레기를 정리한다.
Garbage First GC (G1)

- G1(Garbage First) GC는 장기적으로 많은 문제를 일으킬 수 있는 CMS GC를 대체하기 위해 개발되었고, Java7부터 지원되기 시작하였다.
- G1 GC는 기본 Heap을 Young generation, Old Generation등으로 나누는것은 동일하지만 공간을 region으로 나눠서 동작합니다
- Heap을 일정 크기의 Region으로 잘게 나누어 어떤 영역은 Young Generation, 어떤 영역은 Old Generation으로 사용한다.
- 런타임에 따라 G1 GC가 필요에 따라 영역 별 Region 개수를 튜닝함으로써 Stop The World를 최소화할 수 있다.
- Java 9이상부터는 G1 GC를 기본 GC 실행 방식으로 사용한다.
- G1은 Garbage First의 약어로 Garbage만 있는 Region을 먼저 회수한다고 해서 붙여진 이름이다.
- 빈 공간 확보를 더 빨리 한다는 것은 조기 승격이나 급격히 할당률이 늘어나는 것을 방지하여 Old Generation을 비교적 한가하게 만들 수 있다.
- 장점
- 별도의 STW 없이도 여유 메모리 공간을 압축하는 기능을 제공한다.
- 또한, 전체 Old Generation 혹은 Young Generation 통째로 Compaction을 할 필요 없고, 해당 Generation의 일부분 Region에 대해서만 Compaction을 하면 된다.
- Heap 크기가 클수록 잘 동작한다.
- CMS의 비해 개선된 알고리즘을 사용하고, 처리 속도가 더 빠르다.
- Garbage로 가득찬 영역을 빠르게 회수하여 빈 공간을 확보하므로 GC 빈도가 줄어든다.
- 단점
- 공간 부족 상태를 조심해야 한다. (Minor GC, Major GC 수행하고 나서도 여유 공간이 부족한 경우)
- 이때는 Full GC가 발생하는데, 이 GC는 Single Thread로 동작한다.
- Full GC는 heap 전반적으로 GC가 발생하는 것을 뜻한다.
- 작은 Heap 공간을 가지는 Application에서는 제 성능을 발휘하지 못하고 Full GC가 발생한다.
- Humonogous 영역은 제대로 최적화되지 않으므로 해당 영역이 많으면 성능이 떨어진다.
출처:
https://mangkyu.tistory.com/118
https://steady-coding.tistory.com/590