C#은 메모리 관리를 자동화하는 가비지 컬렉터(Garbage Collector, GC)를 사용합니다. 가비지 컬렉터는 프로그래머가 직접 메로리를 관리하지 않고도 메모리 누수를 방지하고 효율적인 메모리 관리를 가능하게 합니다. 이번 글에서는 C# 가비지 컬렉터의 개념과 동작 원리, 그리고 가비지 컬렉션을 최소화 하는 방법에 대해 알아보겠습니다.
1. C# 가비지 컬렉터(GC)란?
가비지 컬렉터는 더 이상 사용되지 않는 객체를 자동으로 식별하고 해제하여 메모리를 회수하는 메커니즘입니다. C#에서 가비지 컬렉터는 .NET 프레임워크의 일부로, 개발자가 메모리 관리에 신경쓰지 않아도 되도록 합니다. 가비지 컬렉터는 힙(heap) 영역에서 동작하며, 힙은 프로그램 실행 중 동적으로 할당되는 메모리 공간입니다.
2. 가비지 컬렉터의 동작 원리
C#의 가비지 컬렉터는 주로 Mark-and-Sweep 알고리즘을 사용하여 동작합니다. 가비지 컬렉션 과정은 다음과 같은 단계로 이루어 집니다.
1. 마킹(Marking): GC는 루트 오브젝트(root objects)로부터 시작하여 모든 접근 가능한 객체들을 마킹합니다. 루트 오브젝트는 스택에 있는 변수들, CPU레지서트, 정적 변수 등을 포함합니다.
2. 스위핑(Sweeping): 마킹되지 않은 객체들을 힙에서 제거하여 메모리를 회수합니다.
3. 컴펙션(Compaction): 남아 있는 객체들을 힙의 시작 부분으로 이동시켜 메모리 단편화를 줄입니다.
가비지 컬렉션은 세대(generation) 기반으로 동작합니다. 세대는 객체의 생존 기간에 따라 나누어지며, GC는 세대별로 다르게 동작하여 효율성을 높입니다. C#의 GC는 0세대, 1세대, 2세대로 구분됩니다.
- 0세대 (Generation 0): 가장 최근에 할당된 객체들이 위치합니다. 대부분의 경우, 0세대의 객체들은 매우 짧은 수명을 갖고 금방 수집됩니다.
- 1세대 (Generation 1): 0세대에서 살아남은 객체들이 이동하는 공간입니다. 1세대는 중간 단계로, 0세대 보다 수명이 길지만 여전히 상대적으로 짧은 객체들이 위치합니다.
- 2세대 (Generation 2): 1세대에서 살아남은 객체들이 위치합니다. 2세대는 가장 오래된 객체들이 모여 있으며, 가비지 컬렉션이 가장 덜 빈번하게 일어납니다.
- LOH (Large Object Heap): 크기가 85,000 바이트(약 83KB)를 초과하는 큰 객체는 0세대나 1세대가 아닌 LOH에 바로 할당됩니다. 이는 대용량 객체가 자주 할당되고 해제되는 것을 방지하여 메모리 단편화를 줄이고, 선응을 향상시키기 위함입니다. LOH는 주로 2세대 가비지 컬렉션(Full GC 또는 Major GC)에 의해 수집됩니다. 따라서 LOH의 가비지 컬렉션은 빈도가 낮지만 비용이 많이 듭니다.
3. 가비지 컬렉션의 과정
1. 객체 할당: 새로운 객체는 0세대에 할당됩니다. 0세대가 가득 차면 가비지 컬렉션이 발생합니다.
2. 0세대 가비지 컬렉션 (Minor GC): 0세대에서 살아남은 객체들은 1세대로 이동합니다. 0세대에서 살아남지 못한 객체들은 수집되어 메모리가 해제됩니다. 0세대 가비지 컬렉션은 빠르고 자주 발생합니다.
3. 1세대 가비지 컬렉션: 1세대도 가득 차면 가비지 컬렉션이 발생합니다. 1세대에서 살아남은 객체들은 2세대로 이동합니다. 1세대의 가비지 컬렉션은 0세대보다 덜 자주 발생하지만 여전히 자주 발생할 수 있습니다.
4. 2세대 가비지 컬렉션 (Major GC): 2세대도 가득 차면 가비지 컬렉션이 발생합니다. 2세대의 가비지 컬렉션은 가장 드물게 발생하지만, 가장 비용이 많이 듭니다. 2세대에서 살아남은 객체들은 계속 2세대에 남아 있게 됩니다.
5. Full GC: Full GC는 .NET GC에서 가장 강력하고 비용이 많이 드는 프로세스입니다. 이는 0세대와 1세대뿐 아니라 2세대(및 LOH)를 포함한 모든 힙 영역을 대상으로 수행됩니다. Full GC는 일반적으로 메모리가 부족하거나, 명시적으로 호출되었을 때 발생합니다.
4. 장단점
장점
1. 자동 메모리 관리: GC는 객체가 더 이상 필요하지 않을 때 자동으로 메모리를 해제합니다. 이는 메모리 누수를 방지하고, 안정적인 시스템을 개발하는 데 도움을 줍니다.
2. 메모리 누수 방지: 객발자가 메모리를 명시적으로 해제하지 않아도 되기 때문에, 메모리 누수가 발생할 가능성이 줄어듭니다. 이는 특히 복잡한 시스템에서 큰 장점이 됩니다.
3. 안전성 향상: GC는 객체가 더 이상 참조되지 않을 때 메모리를 해제하므로, 잘못된 메모리 접근으로 인한 오류를 방지할 수 있습니다. 이는 프로그램의 안정성을 높입니다.
4. 개발 생산성 향상: 개발자는 메모리 관리에 신경 쓰지 않고 개발에 집중할 수 있습니다. 이는 개발 생산성을 크게 향상시킵니다.
5. 메모리 단편화 감소: GC는 힙 메모리를 재배치하고 압축하여 메모리 단편화를 줄입니다. 이는 메모리 사용의 효율성을 높입니다.
단점
1. 성능 오버헤드: GC는 주기적으로 실행되며, 이 과정에서 애플리케이션이 일시적으로 중단될 수 있습니다. 특히 Full GC는 긴 중단 시간을 유발할 수 있어 성능에 영향을 미칩니다.
2. 예측 불가능한 중단: GC는 예측할 수 없는 시점에 실행될 수 있으며, 이는 프로그램의 응답성에 영향을 줄 수 있습니다. 실시간 응답이 중요한 프래그램에서는 문제가 될 수 있습니다.
3. 메모리 사용 증가: GC가 동작하기 위해서는 일정량의 추가 메모리가 필요합니다. 이는 전체 메모리 사용량을 증가시킬 수 있습니다.
4. 복잡성 증가: GC는 자동화된 시스템이지만, 최적화를 위해서는 메모리 프로파일링 및 관리 전략이 필요합니다.
5. 가비지 컬렉션을 줄이기 위한 방법
가비지 컬렉션을 최소화하기 위한 몇 가지 방법은 다음과 같습니다.
- 객체 재사용: 빈번하게 생성 및 파괴되는 객체를 재사용합니다. 예를 들어, 오브젝트 풀링(Object Pooling)을 사용하여 객체 생성 비용을 줄입니다.
- 구조체 사용: 값 타입(struct)을 사용하여 힙 할당을 줄입니다. 값 타입은 스택에 저장되므로 힙을 사용하지 않습니다.
- 큰 객체 관리: 큰 객체는 LOH(Large Object Heap)에 할당되므로, 큰 객체를 사용을 최소화하고 필요할 때는 분할하여 사용합니다.
- 불필요한 할당 피하기: 임시 객체의 생성을 최소화하고, 필요한 경우 메모리를 명시적으로 해제합니다.
- 최적화된 코드 작성: 코드의 성능을 최적화하여 메모리 사용을 줄입니다. 예를 들어, LINQ를 사용할 때 불필요한 메모리 할당을 줄이기 위해 주의해야 합니다.
6. 정리
C#의 가비지 컬렉터는 메모리 관리를 자동화하여 개발자가 메모리 누수를 걱정하지 않도록 합니다. 가비지 컬렉터는 마킹, 스위핑, 컴팩션 단계를 통해 동작하며, 세대 기반의 관리로 효율성을 높입니다. 가비지 컬렉션을 최소화하기 위해 객체 재사용, 구조체 사용, 큰 객체 관리, 불필요한 할당 피하기, 최적화된 코드 작성 등의 방법을 사용할 수 있습니다. Unity에서는 게임 성능을 위해 더욱 신경써야 하며, 추가적인 최적화 전략과 설정이 필요합니다.
지금까지 C# GC에 대해 알아보았습니다.
현재 글의 내용 중 틀린 부분이나, 지적하실 내용이 있으시다면 언제든지 알려주세요! 읽어주셔서 감사합니다.
'CS 공부' 카테고리의 다른 글
[C#] 대리자 (Delegate) (0) | 2024.07.27 |
---|---|
[C#] 메모리 구조 (0) | 2024.07.22 |
[C#] Virtual, Abstract, Interface (0) | 2024.07.09 |
[C#] 박싱과 언박싱 (Boxing & Unboxing) (0) | 2024.07.02 |
[C#] 해시셋 (HashSet) (0) | 2024.06.28 |