이번엔 저번에 공부했던 JVM
에 이어 GC
에 대해 알아보자.
GC?
GC
는 JVM
내에 존재하는 하나의 부속 머신이다.
Garbage Collector
의 약어로, 말 그대로 쓰레기 청소부 역할을 한다.
Java
의 가장 큰 특징인 자동화된 메모리 관리의 핵심이다.
사용되지 않는 메모리에 대해 누수가 발생하지 않기 위해 도와주는 것이다!
GC 대상
먼저 GC의 대상은 기본적으로 동적 메모리인 Heap
공간이다.
이전에 알아보았듯이, Runtime Data Area
내에는 Heap
이 있다.
해당 공간에 동적으로 할당된 메모리들을 자동으로 해제하는 것이다.
해당 공간은 자세하게 아래와 같이 분할할 수 있다.
- Young generation
- Eden : 생성된지 얼마 되지 않은 객체들
- Survivor : 최소 1번 GC가 실행되어 Eden에서 옮겨온 객체들
- 둘 중 하나는 반드시 비어있어야 한다는 규칙이 있다.
- Old generation
- Old : 생성된지 오래되어 Old로 옮겨진 객체들
- Permanent generation
- Permanent : 클래스의 메타데이터가 저장되는 공간
- Java 8 버전 이후로는 Native method stack으로 옮겨갔다고 한다.
위의 구조를 보았을 때, Young
과 Old
모두 GC의 대상이다.
이 때 Young
을 대상으로 진행하는 Collection
을 Minor GC
라고 한다.
또, Old
를 대상으로 진행하는 Collection
을 Major GC
라고 한다.
Minor GC
기본적으로 Collection
동작은 Mark and Sweep
방식으로 동작한다.
아래와 같은 순서로 Minor GC
가 진행된다.
- 만약
Eden
이 꽉찼다면?Minor GC
가 수행된다. Reachable
, 즉 아직 사용중인 객체에Mark
를 한다.Mark
된 객체들을Survivor
공간으로 옮긴다.- 남은
Unreachable
객체들을Sweep
한다. - 이 때 살아남은 객체들은
Survivor
에 모여있으며,Age
를 하나 증가시킨다. - 이후 흩어진 객체들을 Heap의 시작주소로 모아
Compact
시킨다.
JVM의 GC
에는 Age
개념이 존재한다.
이 Age
가 임계값을 넘어가는 경우, Old
공간으로 옮기게 되는 것이다.
보통은 6bit 값으로 표현되기 때문에 31
을 임계값으로 두고 있다고 한다.
Major GC
이번에는 Old
영역이 가득차면 발생하는 Major GC
에 대해 알아보자.
Major GC
는 흔히 얘기하는 큰 오버헤드의 주범 Full GC
와 같은 말이다.
단순히 Old
의 모든 객체를 싹 검사하여 Unreachable
객체들을 버린다.
삭제하고, 앞서 Minor GC
와 같이 Compact
과정을 진행한다.
하지만 Old
영역은 Young
영역에 비해 매우 크다.
그렇기에, Young
영역을 탐색하는 시간에 비해 훨씬 많은 시간이 걸린다.
우리는 이 시간을 Stop the world
라고 칭한다.
많은 개발자들이 이 오버헤드를 줄이기 위해 많은 연구를 진행했다.
GC 알고리즘
Serial GC
가장 단순한 방식의 GC이다.
그냥 하나의 스레드가 GC를 진행하게 되며, STW
가 매우 길다고 볼 수 있다.
Young
영역은 Mark-Sweep
을 사용한다.
Old
영역은 Mark-Sweep-Compact
를 사용한다.
Parallel GC
Java 8에서 기본적으로 사용하는 GC 방식이라고 한다.
Serial GC
에서 개선하여, Minor GC
를 멀티스레드 방식으로 수행한다.
당연히 멀티스레드 방식이기에 STW
가 조금은 줄어든다.
Parallel Old GC
Old
영역까지 멀티스레드 방식으로 처리하는 개선된 방식이다.
여기서는 Mark-Summary-Compact
라는 새로운 알고리즘이 도입된다.
STW
가 Major GC
에서 크게 발생하는데, 이를 크게 줄일 수 있을 것이다.
Concurrent Mark Sweep
Application
스레드와 GC
스레드를 아예 동시에 실행하는 방식이다.
STW
시간을 최대한 줄일 수 있으나, GC
과정이 매우 복잡해졌다.
GC
에 대한 CPU
사용량도 높아지고, 메모리 파편화도 발생해 사용이 중지되었다.
G1 GC
Garbage First GC
라고도 불리는 알고리즘이다.
CMS GC
를 대체하여 사용하기 위해 고안되었으며, Java 9+
의 기본 GC이다.
여기서부터는 Heap
공간의 구조가 아예 바뀌어버린다.
보통은 Eden, Survivor, Old
로 공간을 크게 3군데로 나누었었다.
하지만 여기서는 공간을 Region
이라는 개념으로 잘라 역할을 동적으로 부여한다.
메모리 전체를 탐색하지 않고, 메모리가 많이 찬 Region
단위로 탐색을 수행한다.
그렇기에 기존의 GC
방식보다는 STW
가 꽤 줄었다고 볼 수 있다.
4GB 이상의 Heap 공간과, 0.5초 정도의 STW를 원할 때 사용하길 권장한다고 한다.
Shenandoah GC
Redhat
에서 개발한 GC
방식이다.
CMS GC
의 단편화를 해결하고, G1
의 Pause 이슈를 해결한 방식이라고 한다.
ZGC
JDK 15와 17에서 GC
로 인정받아 사용되는 최신 방식의 GC
라고 한다.
대량의 메모리를 유연하게 처리하기 위해 고안된 방식이다.
G1 GC
의 Region
개념과 같이 ZPage
공간을 사용한다.
Region
과 다르게 고정 공간이 아닌, 2^N
으로 동적 공간을 할당한다.
Heap의 크기가 증가하더라도 STW
가 10ms
를 넘지 않는다는 것이 큰 장점!
보통 GC 튜닝
을 한다고 하면, 위의 알고리즘들을 고르는 것을 의미한다.
현재 환경에서 과연 어떤 알고리즘이 최적일지 성능을 비교하게 되는 것이다.
GC의 단점
이렇게 엄청 편리할 것 같은 GC
에도 몇 가지 단점이 있다.
- 당연하게도 STW로 인한 오버헤드가 단점일 수 있겠다.
- GC가 실행되는 시점이 언제인지 알 수가 없다.
- 즉, 개발자가 메모리 해제 시점을 예측 할 수 없다!