CLR이 자동 메모리 관리(Automatic Memory Management)기능을 제공
자동 메모리 관리 기능의 중심에 가비지 컬렉션이 있다.
가비지 컬렉터도 소프트웨어이기 때문에 CPU와 메모리 같은 컴퓨팅 자원을 소모한다.
기본적으로 C#으로 작성된 모든 코드는 관리형 코드(Managed Code)에 속한다. 누가 관리하냐고? 바로 CLR.
비관리형 코드(Unmanaged Code)에는 C/C++ 등이 있다.
CLR은 실행되는 코드에 대해 메모리 할당, 보안, 스레딩, 가비지 컬렉팅 등의 일을 수행한다.
C#으로 비관리형 코드(Unmanaged Code)를 작성하기 위해서는 unsafe 키워드를 이용하면 된다. 이 경우에는 CLR이 제공하는 서비스를 받을 수 없다.
“버스의 출입구 쪽에 가까이 있는 승객일수록 버스에서 빨리 내릴 확률이 높고, 출입구로부터 멀리 있는 승객일수록 버스에서 최대한 오래 버틸 확률이 높다.”
CLR은 메모리를 0, 1, 2의 3개 세대로 나누고 0세대에는 빨리 사라질 것으로 예상되는 객체들을, 2세대에는 오랫동안 살아남을 것으로 예상되는 객체들을 위치시킨다. 1세대는 0세대와 2세대의 중간이다.
기본적으로 메모리 할당은 0세대 힙에만 일어난다. 그리고 가비지 컬렉션이 일어날 때 0세대에서 해제되지 않는 객체들은 1세대로 승격된다. 그럼 1세대 가비지 컬렉션이 일어난다면? 0세대와 1세대 모두에 대해서 가비지 컬렉션을 수행하고 0세대에서 살아남은 객체는 1세대로, 1세대에서 살아남은 객체는 2세대로 승격된다. 2세대는 더 이상 말하지 않아도 알 것이라고 생각한다.
참고로 2세대 가비지 컬렉션을 GC 2 혹은 풀 가비지 컬렉션(full garbage collection)이라고 하고 모든 세대에 대해 가비지 컬렉션이 수행되는 것이다.
거기에 더해 LOH도 가비지 컬렉션 된다. LOH는 밑에서 설명한다.
세대별로 가비지 컬렉션의 주기는 보통 0세대가 10회 일어날 때 1세대가 1회 일어나는 정도라고 한다. 마찬가지로 1세대가 10회 일어날 때 2세대가 1회 수준으로 일어나면 괜찮은 수준이라고 한다.
가비지 수집 조건 – MSDN
가비지 수집은 다음 조건 중 하나가 충족될 경우 발생합니다.
- 시스템의 실제 메모리가 부족합니다. 이는 OS의 메모리 부족 알림 또는 호스트에서 표시되는 메모리 부족을 통해 감지됩니다.
- 관리되는 힙의 할당된 개체에 사용되는 메모리가 허용되는 임계값을 초과합니다. 이 임계값은 프로세스가 실행됨에 따라 계속 조정됩니다.
- GC.Collect 메서드가 호출됩니다. 가비지 수집기가 지속적으로 실행되므로 이 메서드를 호출해야 하는 경우는 거의 없습니다. 이 메서드는 주로 특이한 상황 및 테스트에 사용됩니다.
CLR의 가비지 컬렉션은 Managed Heap을 대상으로 일어난다. 사실 이 Managed Heap은 두 종류로 나눠져 있다. 하나는 SOH(Small Object Heap)이고 다른 하나는 LOH(Large Object Heap)이다. SOH는 지금까지 위에서 설명한 3세대로 이루어진 힙이다. 이 힙에는 85KB 이하의 객체들만 할당된다. 그래서 이름이 ‘작은 오브젝트 힙’이다.
LOH는 예상대로 85KB 이상의 크기를 갖는 객체들만 할당된다. 그리고 세대 따위는 없다. 왜 이렇게 구분이 되어있을까 묻는다면 마소의 최강 엔지니어들이 ‘85KB 이상은 따로 관리하는 것이 좋겠더라’ 라는 것이 쉬운 설명이다.
좀 더 들여다보자면 LOH는 기본적으로 가비지 컬렉션이 일어나도 메모리 컴팩션(메모리 압축)은 일어나지 않는다. 그러니까 프로그램을 실행하고 시간이 지나면 메모리 단편화가 일어나고 LOH의 메모리는 중간중간 자유 메모리 공간이 송송 뚫려있는 모습일 것이다.
왜 이렇게 해놨을까? 이유는 ‘크기가 큰 객체’라는 특성 때문이다. 크기가 큰 객체는 메모리 컴팩션을 할 때 작은 객체들에 비해 오버헤드(대부분 메모리를 0으로 초기화하는 시간) 역시 커진다. 이런 이유 때문에 메모리 단편화가 일어나는 문제를 없애기보다도 그냥 놔두는 편이 성능에 효율적이라고 판단한 것이다.
이 힙은 세대가 구분되어 있지 않다. 그리고 2세대 가비지 컬렉션이 일어날 때만 LOH도 가비지 컬렉션이 수행된다.
이유? 역시 당연히 있다. 그리고 역시 위에 설명했던 ‘크기가 큰 객체’라는 특성 때문이다. 보통 2세대 가비지 컬렉션은 0세대 가비지 컬렉션이 100번 수행되어야 1번정도 수행되는 꼴이다. 크기가 큰 객체는 할당하는 것도, 해제하는 것도 큰 일이다. 그래서 되도록 적은 주기로 가비지 컬렉션이 되도록 만든 것이다.
기타 MSDN 문서들