이번 글은 JVM 의 핵심 구성 요소 중 하나인 Garbage Collection (GC) 에 관한 내용입니다. Java 는 C/C++ 처럼 메모리 관리를 직접 해줄 필요가 없단 장점이 있는데, 이 GC 가 어떻게 동작하는지 알아보자.
자바의 Garbage Collection (GC) 는 더 이상 사용되지 않는 데이터가 할당되어 있는 메모리를 해제시켜주는 장치이다. JVM 에서 자동으로 동작하기 떄문에 C/C++ 처럼 코드 상에서 직접 명시해줄 필요가 없다. GC가 주로 동작하는 대상은 Heap 영역 내에서 참조되지 않는 데이터이다.
예를 들면
public class Main {
public static void main(String[] args) {
Foo foo = new Foo("a");
foo = new Foo("b");
//new Foo("a") 은 더 이상 참조되지 않음
}
}
위 코드에서 foo는 처음에 a라는 값을 가진 객체를 참조한다. 하지만 곧 b를 값으로 가진 객체를 참조하게 되어 a를 값으로 가진 객체는 더 이상 참조가 되지 않는다.
이렇게 더 이상 참조되지 않는 데이터를 unreachable 이라고 하는데, 이러한 unreachable 한 데이터들을 GC 는 garbage 라고 인식한다.

힙에 있는 객체들은 다음 4 가지 종류 중 하나로 참조된다
이 4 가지 중 힙 내의 다른 객체에 의한 참조를 제외한 나머지 3개가 root set으로, reachability 를 판가름하는 기준이 되고, reachable 한 객체가 참조하는 객체도 reachable 이 된다.
하지만 root set 에 의해 참조되지 않는 객체들은 unreachable 하다고 판단이 되고 GC 의 대상이 된다.
GC 실행 방식을 알아보기 전 알아야 할 개념이 바로 Stop-The-World 이다. JVM 은 GC 를 통해 unreachable 한 객체를 정리하여 메모리를 확보하는데, 그렇다면 GC 를 자주 실행시키면, 여유 메모리를 최대한 확보하여 성능이 좋아진다고 생각할 수도 있다. 하지만 GC 가 일어나면 GC 를 담당하는 쓰레드를 제외한 모든 쓰레드의 작동이 일시적으로 정지된다. 이를 Stop-The-World 현상이라고 한다. 그래서 흔히 GC 튜닝이란 Stop-The-World 의 시간을 줄이는 것을 말한다.
가비지 컬렉터는 두 가지 가설 하에 만들어졌다.
이 두가지 가설 하에 GC의 아키텍쳐와 동작 방식이 설계되었다.
이 가설의 장점을 최대한 살리기 위해서 크게 Young 영역 과 Old 영역으로 물리적 공간을 나눈다
[이미지 출처: JVM에 관하여 - Part 4, Garbage Collection 기초]
이러한 방식은 메모리에 Fragmentation 이 발생한다. 그래서 이를 보완하기 위해 Mark And Compact Algorithm 을 사용한다.
[이미지 출처: JVM에 관하여 - Part 4, Garbage Collection 기초]
참조되지 않은 객체를 삭제한 후에, 메모리를 정리하여 메모리 단편화를 해결한다. 많은 GC 방식들이 이 알고리즘을 바탕으로 구현되어 있다.
[이미지 출처: JVM에 관하여 - Part 4, Garbage Collection 기초]
먼저 Minor GC 는 Young 영역에서 일어나는 GC이다. Young 에 위치한 각각의 영역이 가득 차게 되어 더 이상 새로운 객체를 생성할 수 없을 때 발생한다. 마크된 영역이 다음 영역으로 복사가 되면서 이루어진다.
HotSpot VM 에서 Young 영역은 3 영역으로 나뉜다
그리고 이 3 영역은 다음과 같은 방식으로 동작한다 :
Old 영역에서 Young 영역의 어떤 객체를 참조하는지 알기 위해 Old 영역에 Card Table 을 저장한다. Minor GC 발생 시 Old 영역이 어떤 객체를 참조하는지 하나하나 확인하는 것이 아닌 이 Card Table 을 참조한다.
추가로 알아두면 좋을 것은, Minor GC 가 발생할 때 Stop-The-World 가 발생하지 않는다고 많이 알려져 있지만, 사실 Minor GC 에서도 발생한다. 하지만 Major GC 와 비교해서 매우 짧은 시간 동안 발생한다.
HotSpot VM 은 Minor GC 를 빠르게 하기 위해 두 가지 기술을 사용한다. 그 중 한 가지가 바로 bump-the-pointer 이다. Young 영역에 있는 Eden 과 Survivor 영역의 최상단을 추적하는 포인터를 둬서 새로운 객체를 생성 할 때 이 포인터를 통해 남은 공간이 충분한지 점검할 수 있으므로 빠르게 메모리 할당이 이루어진다.
멀티 스레드 환경에서 Thread-Safe 하게 여러 스레드에서 사용하는 객체를 Eden 영역에 저장하려면 락이 발생하여 성능이 저하된다. 이 문제를 해결하기 위해 HotSpot VM 에서는 TLABs 를 사용한다.
각각의 스레드가 각각의 몫에 해당하는 Eden 영역의 작은 덩어리를 가진다. 각 쓰레드는 자기가 갖고 있는 TLAB 에만 접근할 수 있기 때문에, bump-the-pointer 를 사용해도 락이 없이 메모리 할당이 가능하다.
Old 영역이 가득 차면 발생한다. 이 과정에서 앞서 설명했던 Mark-And-Compact 방식이 대표적으로 사용된다.
Major GC 에서의 Stop-The-World 시간은 길다
Heap 영역이 모두 가득 차면 발생하며, Young 영역과 Old 영역을 모두 정리한다.
https://tecoble.techcourse.co.kr/post/2021-08-30-jvm-gc/
https://d2.naver.com/helloworld/1329
https://d2.naver.com/helloworld/329631
https://www.perfmatrix.com/how-does-garbage-collector-work/
https://plumbr.io/blog/garbage-collection/minor-gc-vs-major-gc-vs-full-gc