Garbage Collection은 heap 메모리를 관리한다. Java에서는 heap에서 참조하지 못하는 메모리는 JVM이 자동으로 반환해준다.
GC에 대해서 알아보기 전 "stop-the-world"에 대해서 알아보자.
stop-the-world란 GC를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것이다.
Java에서는 개발자가 프로그램 코드로 메모리를 명시적으로 해제하지 않기 때문에 가비지 컬렉터(Garbage Collector)가 더 이상 필요 없는 객체를 찾아 지우는 작업을 한다.
System.gc()
메서드를 호출하는 것은 지양해야 한다. null
로 지정하는 것은 큰 문제가 안 되지만, System.gc()
메서드를 호출하는 것은 시스템의 성능에 매우 큰 영향을 끼치므로 절대로 사용하면 안 된다 이 가비지 컬렉터는 두 가지 가설 하에 만들어졌다.
weak generational hypothesis
이 가설을 바탕으로 HotSpot VM에서는 크게 2가지 물리적 공간을 나누었다. 바로 Young 영역과 Old 영역이다.
Young (Yong Generation) 영역
Old(Old Generation) 영역
GC 영역 및 데이터 흐름도
Young 영역은 3개의 영역으로 나뉜다.
각 영역의 처리 절차를 순서에 따라 기술하면 다음과 같다.
1) 새로 생성한 대부분의 객체는 Eden 영역에 위치한다.
2) Eden 영역에서 GC가 한 번 발생한 후 살아남은 객체는 Survivor 영역 중 하나로 이동된다.
3) Eden 영역에서 GC가 발생하면 이미 살아남은 객체가 존재하는 Survivor 영역으로 객체가 계속 쌓인다.
4) 하나의 Survivor 영역이 가득 차게 되면 그 중에서 살아남은 객체를 다른 Survivor 영역으로 이동한다. 그리고 가득 찬 Survivor 영역은 아무 데이터도 없는 상태로 된다.
5) 이 과정을 반복하다가 계속해서 살아남아 있는 객체는 Old 영역으로 이동하게 된다.
다음 그림은 Minor GC를 통해서 Old 영역까지 데이터가 쌓인 것을 나타낸 것이다.
Old 영역은 기본적으로 데이터가 가득 차면 GC를 실행한다.
GC 알고리즘은 모두 설정을 통해 Java에 적용할 수 있다. 따라서 개발자는 상황에 따라 필요한 GC 방식을 설정해서 사용할 수 있다.
GC 방식에 따라서 처리 절차가 달라진다. GC 방식은 5가지가 있다.
Serial GC (-XX:+UseSerialGC)
운영서버에서 절대 사용하면 안되는 방식이다. Serial GC는 데스크톱의 CPU 코어가 하나만 있을 때 사용하기 위해서 만든 방식이다. GC를 처리하는 쓰레드가 1개이기 때문에 Serial GC를 사용하면 애플리케이션의 성능이 많이 떨어진다. 따라서 stop-the-world 시간이 가장 길다.
Serial GC는 적은 메모리와 CPU 코어 개수가 적을 때 적합한 방식이다.
Parallel GC (-XX:+UseParallelGC)
Parallel GC는 Serial GC와 기본적인 알고리즘은 같다. Young 영역의 Minor GC를 멀티 쓰레드로 수행한다. (Old 영역은 여전히 싱글 쓰레드이다.)
그러나 Serial GC는 GC를 처리하는 스레드가 하나인 것에 비해, Parallel GC는 GC를 처리하는 쓰레드가 여러 개이다.
→따라서 Serial GC보다 빠른게 객체를 처리할 수 있다.
Parallel GC는 메모리가 충분하고 코어의 개수가 많을 때 유리하다.
Parallel GC는 Throughput GC라고도 부른다.
다음은 Serial GC와 Parallel GC의 스레드를 비교한 그림이다.
Parallel Old GC(-XX:+UseParallelOldGC)
Parallel GC를 개선한 버전이다. Parallel GC와 비교하여 Old 영역의 GC 알고리즘만 다르다. Young 영역 뿐만 아니라, Old 영역에서도 멀티 쓰레드로 GC를 수행한다.
CMS GC (-XX:+UseConcMarkSweepGC)
어플리케이션의 쓰레드와 GC 쓰레드가 동시에 실행되어 stop-the-world 시간을 최대한 줄이기 위해 고안된 GC이다.
단, GC 과정이 매우 복잡해진다.
CMS GC의 단점
결국 CMS GC는 Java9 버젼부터 deprecated 되었고 결국 Java14에서는 사용이 중지되었다.
G1(Garbage First) GC
CMS GC를 대체하기 위해 만들어졌다. Java 9+ 버전의 디폴트 GC로 지정되었다.
기존의 GC 알고리즘에서는 Heap 영역을 물리적으로 고정된 Young / Old 영역으로 나누어 사용하였지만,
G1 GC는 아예 이러한 개념을 뒤엎는 Region이라는 개념을 새로 도입하여 사용한다. 전체 Heap 영역을 Region이라는 영역으로 체스같이 분할하여 상황에 따라 Eden, Survivor, Old 등 역할을 고정이 아닌 동적으로 부여하여 사용한다.
G1 GC는 바둑판의 각 영역에 객체를 할당하고 GC를 실행한다. 해당 영역이 꽉 차면 다른 영역에서 객체를 할당하고 GC를 실행한다.
→ 즉, 지금까지 설명한 Young의 세가지 영역에서 데이터가 Old 영역으로 이동하는 단계가 사라진 GC 방식이라고 이해하면 된다.
G1 GC의 효율성
이전의 GC들처럼 일일히 메모리를 탐색해 객체들을 제거하지 않는다. 메모리가 많이 차있는 영역(region)을 인식하는 기능을 통해 메모리가 많이 차있는 영역을 우선적으로 GC한다.
→ 즉, G1 GC는 Heap Memory 전체를 탐색하는 것이 아닌 영역(region)을 나눠 탐색하고 영역(region)별로 GC가 일어난다.
또한 이전의 GC 들은 Young Generation에 있는 객체들이 GC가 돌때마다 살아남으면 Eden → Survivor0 → Survivor1으로 순차적으로 이동했지만, G1 GC에서는 순차적으로 이동하지는 않는다.
대신 G1 GC는 더욱 효율적이라고 생각하는 위치로 객체를 Reallocate(재할당) 시킨다.
ex. Survivor1 영역에 있는 객체가 Eden 영역으로 할당하는 것이 더 효율적이라고 판단될 경우 Eden 영역으로 이동시킨다.