[C#] 가비지 컬렉터 (Garbage Collector) 의 원리, 동작 메커니즘


CLR은 어떻게 메모리에 객체를 할당하는가?

C#으로 작성한 소스코드를 컴파일하고 이 파일을 실행하면,

CLR은 이 프로그램을 위한 일정 크기의 메모리를 확보한다.

C-런타임처럼 메모리를 쪼개는 일은 하지 않는다.


넓은 메모리 공간을 통째로 확보해서 하나의 관리되는 힙 (Managed Heap)을

마련한다. 그리고 CLR은 이렇게 확보한 관리되는 힙의 첫 번째 주소에

"다음 객체를 할당할 메모리의 포인터"를 위치 시킨다.




여기에 첫 번째 객체를 할당해보자.

object A = new object();


CLR이 코드를 실행하면 "다음 객체를 할당할 메모리의 포인터"가

가리키고 있는 주소에 A객체를 할당하고 포인터를 A 객체가

차지하고 있는 공간 바로 뒤로 이동시킨다.



또 다른 객체를 하나 만들어보자.

object B = new object();




보시다시피 CLR은 객체가 위치할 메모리를 할당하기

위해 메모리 공간을 쪼개 만든 연결 리스트를 탐색하는 시간과

재조정하는 작업도 필요하지 않다. 그저 메모리만 할당할 뿐


그렇다면 언제, 어떻게 메모리에서 해제될까?

일단 쓰레기인지 아닌지를 판별해야 하지 않을까?


if (true){

object a = new object();

}

객체의 내용물은 힙에, A가 위치한 힙 메모리 주소는 a에 있다.



if 의 블록이 끝나면 a는 없어지게 된다.

그렇다면 A는..?




a를 잃은 채 힙에 남아 있는 객체 A는 이제 코드의 어디에서도

접근할 수 없기 때문에 더 이상 사용할 수 없게 되었다.

즉 쓰레기가 되고, 가비지 컬렉터가 집어가게 된다.



가비지 컬렉터의 원리, 동작 메커니즘

사라져버린 a처럼 할당된 메모리의 위치를 참조하는 객채를 일컬어

루트(Root)라고 부른다. 루트는 a의 경우처럼 스택에 생성될 수도 있고

정적 필드처럼 힙에 생성될 수도 있다. .NET 에플리케이션이 실행되면

jit 컴파일러가 이 루트들을 목록으로 만들고, CLR은 이 루트 목록을

관리하며 상태를 갱신한다. 이 루트가 중요한 이유는 가비지 컬렉터가

CLR이 관리하고 있던 루트 목록을 참조해서 쓰레기를 수집하기 때문.



1. 작업을 시작하기 전에, 가비지 컬렉터는 모든 객체가 쓰레기라고 가정.

루트 목록 내의 어떤 루트도 메모리를 가리키지 않는다고 가정.


2. 루트 목록을 순회하면서 각 루트가 참조하고 있는 힙 객체와의

관계 여부를 조사. 루트가 참조하고 있는 힙의 객체가 또 다른 힙 객체를

참조하고 있다면 이 역시도 해당 루트와 관계가 있는 것으로 판단.

어떤 루트와도 관계가 없다면 쓰레기로 간주.


3. 쓰레기 객체가 차지하고 있던 메모리는 이제 '비어있는 공간'


4. 루트 목록에 대한 조사가 끝나면, 가비지 컬렉터는 이제 힙을

순회하면서 쓰레기가 차지하고 있던 '비어있는 공간'에 쓰레기의

인접 객체들을 이동시켜 차곡차곡 채워 넣는다.




가비지 컬렉터의 원리, 동작 메커니즘에 대해 살펴보았다.

다음 포스팅에서는 가비지 컬렉션의 성능을 높이기 위한

'세대별 가비지 컬렉션' 알고리즘에 대해 알아보겠다.

+ Recent posts