가비지컬렉션은 Java의 메모리 관리 방법 중 하나로 JVM의 Heap 영역에서 동적으로 할당됐던 메모리 중 필요없게 된 메모리 객체를 모아서 주기적으로 제거하는 프로세스를 말한다.
C 또는 C++ 언어에서는 가비지 컬렉션이 없어서 프로그래머가 직접 수동으로 메모리 할당과 해제를 해줘야하는 번거로움이 있지만, Java에서는 가비지컬렉터가 메모리 관리를 대신 해주기 때문에 효율적으로 메모리를 사용할 수 있게 해주며, 개발자는 개발에만 집중 할 수 있게 해준다.
가비지 컬렉션은 특정 객체가 garbage인지 아닌지를 도달성, 도달능력(Reachability)이라는 개념을 적용한다.
객체에 레퍼런스가 있으면 Reachable로 구분되며, 객체에 유효한 레퍼런스가 없으면 Unreachable로 구분 후 수거한다.
Reachable: 객체가 참조되고 있는 상태
Unreachable: 객체가 참조되고 있지 않은 상태
간단한 원리는 가비지 컬렉션이 될 대상 객체를 식별(Mark) 하고 제거(Sweep) 하며 객체가 제거되어 파편화된 메모리 영역을 앞에서 부터 채워나가는 작업(Compaction)을 수행한다.
먼저 Root Space로부터 그래프 순회를 통해 연결된 객체들을 찾아내어 각각 어떤 객체를 참조하고 있는지 찾은 후 마킹하는 과정
※Root Space란?: 힙 영역에 있는 객체들을 참조하고 있는 메모리 영역의 집합 (ex. Stack의 로컬변수, Method Area의 Static 변수)
참조하고 있지 않은 객체 즉 Unreachable 객체들을 Heap에서 제거한다.
Sweep 후에 분산된 객체들을 Heap의 시작 주소로 모아 메모리가 할당된 부분과 그렇지 않은 부분으로 압축한다.
JVM의 힙(Heap) 영역은 동적 레퍼런스 데이터가 저장되는 공간으로서, 가비지 컬렉션에 대상이 되는 공간이다.
Heap영역은 처음 설계될 때 다음의 2가지를 전제 (Weak Generational Hypothesis)로 설계되었다.
즉, 객체는 대부분 일회성되며, 메모리에 오랫동안 남아있는 경우는 드물다는 것이다.
이러한 특성을 이용해 JVM 개발자들은 보다 효율적인 메모리 관리를 위해, 객체의 생존 기간에 따라 물리적인 Heap 영역을 나누게 되었고 Young 과 Old 총 2가지 영역으로 설계하였다.
새롭게 객체가 할당 (Allocation)되는 영역
대부분의 객체가 금방 Unreachable 상태가 되기 때문에, 많은 객체가 Young 영역에 생성되었다가 사라진다.
Young 영역에 대한 가비지 컬렉션 (Garbabe Collectio)을 Minor GC라고 부른다.
Young 영역에서 Reachable 상태를 유지하여 살아남은 객체가 복사되는 영역
Young 영역보다 크게 할당되며, 영역의 크기가 큰 만큼 가비지는 적게 발생한다.
Old 영역에 대한 GC를 Major GC 또는 Full GC라고 부른다.
여기서 그치지 않고 또 다시 힙 영역은 더욱 효율적인 GC를 위해 Young 영역을 3가지 영역(Eden, survivor0, survivor 1)으로 나눈다.
new 를 통해 새로 생성된 객체가 위치.
정기적인 쓰레기 수집 후 살아남은 객체들을 Survivor 영역으로 보냄
최소 1번 이상 GC에서 살아남은 객체가 존재하는 영역
Survivor영역엔 특별한 규칙이 있는데, Survivor 0 또는 Survivor 1 둘 중 하나에는 꼭 비어 있어야 한다.
이렇게 하나의 힙 영역을 세부적으로 쪼갬으로서 객체의 생존 기간을 면밀하게 제어하여 가비지 컬렉터(GC)를 보다 정확하게 불필요한 객체를 제거하는 프로세스를 실행하도록 한다.
Minor GC란 Young Generation 영역에서 발생되는 GC를 일컫는다.
Young Generation 영역은 짧게 살아남는 메모리들이 존재하는 공간이며, 모든 객체는 처음에는 Young Generation에 생성되게 된다.
Young Generation의 공간은 Old Generation에 비해 상대적으로 작기 때문에 메모리 상의 객체를 찾아 제거하는데 적은 시간이 걸린다. (공간의 크기가 작은 곳에서 찾기 때문)
객체가 계속 생성되어 Eden 영역이 꽉차게 되면 Minor GC가 실행
Mark 동작을 통해 Reachable 객체를 탐색
Eden 영역에서 살아남은 객체는 1개의 Survivor 영역으로 이동
Eden 영역에서 사용되지 않은 객체 (Unreachable)의 메모리를 해제 (Sweep)
살아남은 모든 객체들은 age의 값이 1씩 증가
Survivor 영역에서 객체의 객체가 살아남은 횟수를 의미하는 값이며, Object Header에 기록된다.
만일 age 값이 임계값에 다다르면 Promotion(Old 영역으로 이동) 여부를 결정한다.
JVM 중 가장 일반적인 HotSpot JVM의 경우 이 age의 기본 임계값은 31이다.
객체 헤더에 age를 기록하는 부분이 6bit 로 되어 있기 때문이다.
Marking 한 객체들을 비어있는 Survival1으로 이동하고 Sweep한다.
살아남은 모든 객체들은 age값이 1씩 증가
1 ~ 9 까지의 과정들을 계속해서 반복하게 된다.
Old Generation은 길게 살아남는 메모리들이 존재하는 공간이다.
Old Generation의 객체들은 거슬러 올라가면 처음에는 Young Generation에 의해 시작되었으나, GC 과정 중에 제거되지 않은 경우 age 임계값이 차게되어 이동된 경우이다.
Major GC는 Minor GC의 객체들이 계속 Promotion되어 Old 영역의 메모리가 부족해지면 발생하게 된다.
Young Generation 영역의 객체의 임계값에 (해당 자료는 8로 가정) 도달하게 될 경우
해당 객체들은 Old Generation으로 이동되며, 이를 Promotion 이라 부른다.
이 과정이 반복되며 Old Generation 영역의 공간(메모리)가 부족하게 되면 Major GC가 발생하게 된다.
Major GC는 Old Generation 영역의 데이터가 가득 차면 GC를 실행하는 단순한 방식이다.
Old 영역에 할당된 메모리가 허용치를 넘게 되면, Old 영역에 있는 모든 객체들을 검사하여 참조되지 않는 객체들을 한꺼번에 삭제하는 Major GC가 실행되게 된다.
하지만 Old Generation은 Young Generation에 비해 상대적으로 큰 공간을 가지고 있어, 이 공간에서 메모리 상의 객체 제거에 많은 시간이 걸리게 된다.
Young Generation 영역은 Old 보다 크기가 작기 때문에 GC가 보통 0.5 ~ 1초 사이에 끝난다. 따라서 Minor GC는 애플리케이션에 크게 영향을 주지 않는다.
하지만 Old 영역의 Major GC는 일반적으로 Minor GC보다 오래 걸리며, 10배 이상의 시간을 사용한다.
Major GC가 일어나면 Thread가 멈추고 Mark and Sweep 작업을 해야 해서 CPU에 부하를 주기 때문에 멈추거나 버벅이는 현상이 일어나기 때문에 Stop-The-World 같은 문제가 발생하게 된다.
GC를 실행하기 위해 JVM이 모든 애플리케이션 실행을 멈추는 것
stop-the-world가 발생하면 GC를 실행하는 쓰레드를 제외한 나머지 쓰레드는 모두 작업을 멈춘다.
GC작업을 완료한 이후에 작업을 다시 시작한다.
어떤 가비지 컬렉터 알고리즘을 사용하더라도 stop-the-world는 발생한다.