가비지 컬렉션(Garbage Collection)

이지호·2021년 11월 30일
0

가비지 컬렉션(Garbage Collection)이란

어플리케이션이 실행중에 더이상 참조하지 않는 즉, 필요없는 메모리가 발생하게 되면, C언어에서는 개발자가 직접 malloc함수를 통해 메모리를 해제해야한다.

하지만, JVM기반의 자바나 코틀린에서는 JVM내의 가비지컬렉션(Garbage Collection)이 불필요한 메모리를 정리한다.

물론 개발자가 직접 system.gc()를 호출하여 명시적으로 GC를 수행할 수 있지만, Full GC를 불러오게 되어 비용이 많이 들어가는 작업이며, 또한 즉시 작업을 수행한다는 보장이 없어 지양되는 메서드이다.

가비지 컬렉션의 동작방식

가비지 컬렉션이 실행되면, 다음과 같은 절차가 이루어진다.
1. Stop The World
2. Mark and Sweep

Stop The World

Stop The World는 GC(Garbage Collection)이 수행될때 전체 어플리케이션이 멈추는 단계이다.
GC를 수행하는 스레드를 제외한 모든 스레드가 작업을 멈추고 대기한 후 GC가 끝난 후에 작업을 재개한다.
GC의 성능개선은 이 Stop The World 단계의 시간을 줄이는 작업이다.

Mark and Sweep

Mark: GC의 대상을 판별하는 작업
Sweep: Mark된 메모리를 해제하는 작업
Stop The World단계에서 GC는 Root Set의 스레드에서 Local Variables, Operand Stack를 탐색하여 참조가 없는 객체와 Root Set에서 직접참조를 하고 있지 않고, Method Area의 상수풀에서 간접 참조가 있는지 없는지를 탐색한 후 참조가 없는 객체에 대해서도 Mark하고, 메모리를 해제하게 된다. 이 작업을 Sweep라고 한다.

가비지 컬렉션의 관리대상

JVM을 통한 어플리케이션 실행과정을 살펴보면, 객체는 JVM의 Heap에 할당되는데, Heap은 Young GenerationOld Generation로 나누어서 객체를 관리한다.
다음으로 Root Set의 각 스레드들은 필요에 따라 Heap영역내의 객체를 참조하며, 작업을 수행하게 된다.

Root Set과의 관계가 없는 해당 객체는 접근불가능(Unreachable)상태가 되고, 가비지 콜렉션의 관리 대상이 된다.
가비지 컬렉션은 Garbage를 모으는 작업으로써 Unreachable객체를 판별하는 것보다 Reachable객체를 판별하여 작업을 수행하는게 더 효율적이다.

가비지 컬렉션의 메커니즘은 "weak generational hypothesis"에 기반하고 있다.

weak generational hypothesis

  • 대부분의 객체는 금방 접근 불가능 상태가된다.
  • 오래된 객체에서 젊은 객체로의 참조하는 경우는 매우 적다.

첫번째 가설을 보면 "대부분의 객체는 금방 접근 불가능 상태가된다."라고 나와있다.

이를 생각해보면, 기존의 할당된 객체의 다음주소에 객체가 할당될 것이고, 그렇다는 말은 주로 먼저 할당된 객체는 GC(Garbage Collection)의 관리대상이 되어 메모리 해제가 되어 메모리 단편화가 발생하며, Compaction같은 비용이 높은 작업을 수행해야한다.

이를 해결하기 위해 Heap의 Young Generation을 Eden AreaSurvivor Area 두개로 나누어서 관리한다.

Young Generation, Old Generation

Young Generation

Young Generation은 새로 생성된 객체가 할당되는 영역이며, Eden Area와 2개의 Survivor Area로 나누어진다.
대부분의 객체는 금방 Unreachable상태가 되며, Young Generation에서 메모리 해제대상이 된다.
Young영역에 대한 가비지 컬렉션Minor GC라고 한다.

Old Generation

Old Generation은 Young Generation에서 Reachable상태를 유지한 인스턴스가 복사되는 영역이다. 복사되는 과정에서 Young Generation보다 보통 크게 할당되며, 보통 가득차게 되면 GC를 수행하게 된다.
Old영역에 대한 가비지 컬렉션Major GC라고 한다.

여기서 Old Generation에 있는 인스턴스가 Young Generation의 객체를 참조하는 경우도 있는데, 이를 위해 Old Generation에는 512byte의 크기의 덩어리(chunk)로 이루어진 카드 테이블(Card Table)이 존재한다.

카드테이블(Card Table)

카드테이블(Card Table)은 Old영역의 객체가 Young영역의 객체를 참조할 경우를 대비하여 만들어진 것이다.

카드테이블에는 Old Generation의 객체가 Young Generation 객체를 참조할 때, Old Generation의 해당 객체의 시작주소에 카드를 Dirty로 표시하고 해당 내용을 카드테이블에 기록한다.

GC(Garbage Collection)는 Old Generation의 모든 객체를 탐색하지 않고, 카드테이블을 탐색하여 GC를 수행할 수 있다.

Eden Area, Survivor Area

Young Area의 Eden Area, Survivor Area로 구성되어있는데, Young Area에서 GC처리 절차는 다음과 같다.

  • Eden Area는 새로 생성한 대부분의 객체들이 할당되는 영역이다.
  • 2개의 Survivor Area중 한 영역은 GC가 발생한 후 Eden Area에서 살아남은 객체가 이동하는 영역이다.
  • 또 한번 GC가 발생하면, Eden Area에서 살아남은 객체가 존재하는 Survivor Area로 이동한다.
  • Survivor Area가 꽉 차게 되고, 그 중에서 살아남은 객체는 다른 Survivor Area로 이동한다.
  • 이 과정을 반복하면서 계속해서 살아남은 객체는 Old영역으로 이동한다.

bump-the-pointer, TLAB

HotSpot VM에서는 보다 빠른 메모리 할당을 위해 bump-the-pointer, TLAB(Thread-Local Allocation Buffers)라는 기술을 이용한다.

먼저, bump-the-pointer는 Eden Area에 할당된 마지막 객체를 추적하는데 이 객체는 Eden Area의 맨 위에 위치해 있다. 그 다음에 새로 할당되는 객체가 있으면, 해당 객체의 크기가 Eden Area에 넣기 적정한지 확인만 하면된다.

따라서 새로운 객체가 할당될 때, 마지막 객체에 대해서만 추적을 하면 되기 때문에 더욱 빠른 메모리 할당이 가능해진다.

하지만, 이 방법은 멀티스레드 환경에서는 Thread-safe를 위해 여러 스레드가 사용하는 객체를 Eden Area에 할당하기 위해서는 Lock이 발생하게 되어 성능이 저하된다.

이를 해결하기 위해 TLAB이라는 기술을 사용한다.
이는 각각의 스레드가 Eden Area의 작은 영역들을 나누어서 관리하게 하는 것이다.
각각의 스레드는 자기 갖고있는 TLAB을 이용하여 메모리할당을 하면 되기 때문에 bump-the-pointer를 이용해도 문제가 없다.

0개의 댓글