가비지 컬렉션(GarbageCollection, GC)의 개념과 동작원리
GC(GarbageCollection)를 한 마디로 표현하자면 '동적으로 할당되지 않은 메모리 중 필요없는 메모리를 비워버리는 것' 이라고 할 수 있다. C언어의 경우 메모리관리를 개발자가 직접 해주어야 하지만 자바는 GC가 이 작업을 대신 해준다.
개발자가 직접하지 않는다는 것은 꽤나 큰 이점을 가지고 있다.
장점
- 실수로 인한 메모리 누수를 방지해준다.
- 해제된 메모리에 접근을 막아준다.
- 해제한 메모리를 다시 해제하는 것을 막아준다.
단점
- GC작업은 오버헤드를 불러올 수 있다.
- 개발자가 언제 GC가 메모리를 해제하는지 알기 힘들다.
가비지 컬렉션 구조
JVM의 구조 중 '스택(stack)과 힙(heap)'이라는 곳이 있다. 이곳은 객체 데이터가 저장되는 곳이다.
- 스택(stack) : 정적으로 할당된 메모리 영역(기본타입 데이터의 값, 객체의 주소가 저장된다.)
- 힙(heap) : 동적으로 할당된 메모리 영역(객체가 저장된다.)
실행메소드가 종료되면 stack의 데이터는 전부 사라지게 되고 heap은 남아있게 된다. 또는 객체를 선언후 선언한 변수를 null로 변경해도 생성된 객체는 참조되지 않는 객체가 되어 Garbage(가비지)가 되어버린다.
힙에 저장된 객체는 스택영역의 변수나 다른 객체의 필드에서 참조한다. 만약 참조되어지고 있는 주소가 사라진다면 그 객체는 GC에 의해서 사라지게 된다.
GC는 heap에 저장된 동적 데이터들 중에서 더이상 그 어디서도 참조되지 않는 데이터들을 정리해서 공간을 확보하는 역할을 하는 것이다.
이때 참조되고 있는 데이터를 Reachable, 참조되지 않는 데이터를 Unreachable이라고 한다.
GC의 동작순서는 다음과 같다.
- 루트영역에서 어떤 객체를 참조하고 있는 지 스캔한다. 이 작업을 Marking이라고 한다.
- 참조되지 않는 객체(Unreachable Object)가 참조하고있는 객체도 Marking한다.
- 참조되지 않고 있는 객체를 모두 제거한다. 이 작업을 Sweep이라고 한다.
위의 작업을 수행하는 것을 'Mark and Sweep' 이라고 한다.
Heap영역과 GC의 동작
GC는 결국 heap영역에서의 데이터를 제거하고 공간을 확보하는 작업이다.
그렇다면 이번에는 heap영역이 어떻게 이루어져 있는지 살펴보자.
eap영역은 크게 'Young Generation'과 'Old Generation'으로 나누어져 있다. 여기서 Young Generation영역이 다시 3개로 나뉘게 되는데 Eden, Survivor0, Survivor1로 나뉘어지게 된다.
Heap영역안에서 GC는 다음과 같은 과정을 거쳐서 동작한다.
- 객체가 생성되면 Eden에 할당된다. Eden이 가득차면 GC가 발생하는데 이때 동작하는 GC의 이름이 MinorGC이다.
- Eden이 가득차면 Mark and Sweep이 일어나고 이후에 제거되지 않고 살아남은 데이터가 Survivor 0에 이동하게 된다.
- Survivor에 저장할 때는 반드시 저장중이던 곳에만 계속해서 저장을 시킨다. 0에 저장한다면 1은 반드시 비어있어야한다.
- 다시 새로운 데이터는 Eden으로 들어가고 위와 같은 상황을 반복시킨다.
- 작업을 반복하다가 Survivor 0이 가득차게 되면 다시 한번 Mark and Sweep이 일어나게 되고 살아남은 데이터는 Survivor 1로 이동하게 된다.
- Survivor에서 살아남은 데이터가 다른 Survivor로 이동할 때는 이동하는 데이터에 age값을 증가시킨다. age값이 특정값에 도달하게되면 그때 Old Gerneration으로 이동시킨다. 이 작업을 Promotion이라고 한다.
- 언젠가 Old Gerneration도 가득차게 될 텐데 이때 일어나는 GC를 MajorGC라고 한다.
Young영역에서 일어나는 GC를 MinorGC라고 부르고
Old영역에서 일어나는 GC를 MajorGC라고 부른다.
기본적으로는 Young 영역이 Old영역보다 크기가 작게 설정된다.
처음 heap이 설계될 때는 2가지를 전제하고 설계가 되었다.
- 대부분의 객체는 금방 접근 불가능한 상태(Unreachable)가 된다.
- 오래된 객체에서 새로운 객체로의 참조는 아주 적게 일어난다.
실제로 데이터가 생성되고 사라지는 정도를 그래프로 나타낸 것을 살펴보면 대부분의 객체가 MinorGC에서 사라지는 것을 볼 수 있다.
Y축은 할당된 바이트의 수를 표시하고, X축은 시간에 따라 할당된 바이트의 수를 표시한다.
그래프를 보면 대부분의 객체가 왼쪽에서 더 높은 것을 볼 수 있다.
GC의 종류
GC가 동작할 때는 GC를 동작시키는 스레드를 제외한 모든 스레드를 중지시키며 동작한다. 그 말은 모든 작업이 중단된다는 것을 의미한다. 이것을 stop the world라고 한다.
모든 작업이 중단된다는 것은 그 만큼 성능이 떨어진다는 의미가 된다. 그렇기 때문에 GC는 계속해서 stop the world의 시간을 줄일 수 있는 방법들을 고민하며 발전되어 왔다.
GC의 종류로는 크게 4가지가 있다.
- Serial GC
- Parallel GC
- CMS(Concurrent Mark Sweep) GC
- G1(Garbage First) GC
(1) Serial GC
GC를 처리하는 스레드가 1개이다.
스레드가 1개이기 때문에 다른 GC에 비해서 상대적으로 stop the world의 시간이 길다.
(2) Parallel GC
Java8의 default GC이다.
멀티스레드를 사용하기 때문에 stop the world의 시간이 Serial GC에 비해 짧다.
(2-1) Parallel Old GC
Parallel GC를 개선하였다.
Old영역도 멀티스레드로 수행한다.
(3) CMS(Concurrent Mark Sweep) GC
stop the world의 시간을 줄이기 위해서 고안됐다.
Initial Mark, Concurrent Mark, Remark, Concurrent Sweep의 과정을 수행한다.
compact 기능이 없다는 단점이 있다.
* compact는 heap의 영역을 정리하는 단계로 유효한 객체를 앞쪽으로 몰아서 유효한 객체와 유효하지 않은 객체를 나누는 작업이다.
(4) G1(Garbage First) GC
Java 9이상에서 default GC
CMS GC의 compact 과정을 개선하기 위해 만들었다.
heap영역을 물리적으로 나누지 않고 Region이라는 일정한 크기의 단위로 나누었다.
GC를 수행할 때 할당된 Region만 찾으면 된다.