1. Garbage Collector란?
프로그램이 운영체제로부터 할당받는 대표적인 메모리 공간이 있는데,
Code (우리가 만든 코드), Data (전역, 스태틱 변수), Stack (지역, 매개 변수), Heap (동적 할당 변수) 으로 나누어져 있다.
이 중, Code와 Data는 당연히 프로그램의 시작과 종료 시점에 메모리 할당과 해제가 이루어지고,
Stack은 사용 영역이 종료되는 시점 즉, 스코프 ( { } ) 가 종료되는 시점에 LIFO 순서로 할당 해제된다.
문제는 Heap인데, 동적 할당된 변수는 메모리 해제 지점이 불명확하기에 직접 메모리 해제를 해야한다. (Memory Leak)
C, C++ 같은 Unmanaged 환경에서는 사용자가 직접 코드를 통해 해제 명령 (free, delete) 을 내려줘야 했지만,
(물론, RAII 스마트 포인터를 사용하는 방법도 있다.)
C#, JAVA로 대표되는 Managed 환경에서는 Garbage Collector라는 기능을 통해 Heap 영역을 자동으로 관리해준다.
2. C#의 GC 사용처
직역하면 '쓰레기 수집가' 라는 뜻이 되는 가비지 컬렉터는
.NET에서 제공하는 기능이며, 메모리에서 더 이상 사용되지 않는 객체들을 자동으로 정리해준다.
C#은 변수 타입이 두 가지로 나누어지는데,
값 ( int, char, float, enum, struct ) 와 참조 ( class, string, object ) 타입이다.
값 타입은 Stack에 저장되고 해제되기에 따로 관리할 필요는 없고,
참조 타입은 Stack에는 주소가 저장되고 Heap에 데이터가 저장되어 관리가 필요하다.
즉, 참조 타입의 변수를 메모리에서 제거할 때 가비지 컬렉터가 주로 사용된다.
3. C# GC의 작동 방식
가비지 컬렉터가 Heap을 알아서 정리해준다는 것은 알았다.
그렇다면, 가비지 컬렉터는 어떻게 Heap을 정리해주는 것일까?
C#은 .NET (Unity는 Mono 혹은 IL2CPP)의 CLR (공용 언어 런타임)이 제공하는
Managed Heap이라는 Heap을 사용한다.
말 그대로 '관리되는 힙' 이라고 불리는 이 영역은 포인터를 통해서
다음 객체가 할당될 메모리 지역을 메모리의 포인터로 가리킨다.
GC가 메모리를 제거할 때는 여러가지 방법을 사용한다.
- Reference Counting
Reference counting은 참조 횟수 기반 GC의 구성 알고리즘이다.
참조 카운팅의 동작 방식은 간단하다.
동적으로 할당된 메모리 주소가 참조될 때마다 count를 1 증가시키고, 참조를 끊을 땐 1 감소시킴으로써
count를 체크하여 0이 될 경우 (더 이상 참조되지 않는 메모리) 즉시 메모리를 해제하는 방식이다.
C#에서는 잘 사용되지 않고, Python과 C++에서 가끔 사용된다.
C++의 스마트 포인터 std::shared_ptr가 참조 카운트 방식으로 순수 동작한다.
- Mark & Sweep
Mark & Sweep은 추적 기반 GC의 구성 알고리즘이다.
가비지 컬렉터에는 GC Root라는 것이 있다.
GC Root들은 힙 외부에서 접근할 수 있는 변수나 오브젝트를 뜻한다.
GC Root는 말그대로 가비지 컬렉션의 Root라는 뜻이다.
GC Root에서 시작해 이 Root가 참조하는 모든 오브젝트, 또 그 오브젝트들이 참조하는
다른 오브젝트들을 탐색해 내려가며 마크(Mark)한다. 이게 바로 가비지 컬렉션의 첫번째 단계인 Mark단계이다.
Mark가 끝나면 가비지 컬렉터는 힙 내부를 전체를 돌면서 Mark되지 않은 메모리들을 해제한다.
이 과정을 Sweep이라고 부른다.
C#에서는 이 방식과 아래의 세대별 크기별 관리를 통해 GC를 구성하였다.
- 세대별 관리법
이 외에도 CLR은 객체를 세대별로 나누어 관리하여 메모리를 좀 더 효율적으로 관리하는데,
0세대는 GC가 적용된 적이 없는 객체
2세대는 GC가 2번 이상 적용되었지만 아직 남아있는 객체 (즉, GC Root가 존재해 Sweep 되지 않은 객체)
1세대는 0세대와 2세대의 중간이다.
세대가 낮을수록 해제될 확률이 높기 때문에 낮은 세대부터 관리하게 된다.
2세대 객체로 인해 힙이 가득 차는 현상을 Full GC라고 하는데 이 때 프로그램을 일시 중단하고 다시 GC를 실행한다.
이런 방법들로 GC가 메모리에서 객체를 제거하고 나면
제거된 공간들을 하나로 모아 재활용 할 수 있게 해주는 메모리 컴팩션을 진행한다.
이로 인해, 단편화(fragmentation, 빈 공간 또는 자료가 여러 개의 조각으로 나뉘는 현상) 를 줄일 수 있다.
- 객체 크기별 관리법
C# 에서는 85KB 를 기준으로 객체를 SOH( Small Object Heap ), LOH( Large Object Heap )으로 나누는데,
LOH 는 SOH와 달리 할당과 동시에 2세대로 시작한다.
LOH는 메모리 재배치 시 오버헤드가 크기 때문에 재배치를 하지 않기에 사용 시 주의해야 한다.
C#에서는 .NET이 Mark & Sweep + 세대별 관리법 + 객체 크기별 관리법 + α 를
사용하여 구성된 추적 기반 GC를 주로 사용한다.
[C++] Reference Counting
개요 Reference Counting은 객체의 소유권 관리( = 라이프 사이클 )의 방법 중 하나로 객체를 참조(포인팅) 하고 있는 횟수를 추적하여 그 횟수가 0이 되면 메모리에서 해제(소멸)한다. 대부분의 Managed L
nobilitycat.tistory.com
https://imasoftwareengineer.tistory.com/103
'C#' 카테고리의 다른 글
[C#] 대리자 (Delegate, Event) (0) | 2022.04.26 |
---|---|
[C#] 참조형 (Reference) (0) | 2022.04.25 |
[C#] 매개 변수 키워드 (params, ref, out, in) (0) | 2022.04.23 |
[C#] 가상, 추상, 인터페이스 비교 (0) | 2022.01.18 |