가비지 컬렉션
가비지 컬렉션
.NET의 가비지 컬렉터는 자동 메모리 관리 시스템입니다. 명시적으로 객체를 해제할 필요 없이, 사용하지 않는 객체를 자동으로 탐지하여 메모리를 회수합니다. 이로 인해 메모리 누수 위험이 줄고, 개발자의 부담도 완화됩니다.
가비지 컬렉터를 아시나요?
.NET의 가비지 컬렉터는 자동 메모리 관리 시스템입니다. 명시적으로 객체를 해제할 필요 없이, 사용하지 않는 객체를 자동으로 탐지하여 메모리를 회수합니다. 이로 인해 메모리 누수 위험이 줄고, 개발자의 부담도 완화됩니다.
.NET GC는 다음과 같은 특징을 가집니다.
- 정리되지 않은 객체를 추적하고 제거
- 객체 간 참조 관계를 고려하여 살아있는 객체를 구분
- 응용 프로그램의 성능을 최대한 저해하지 않도록 최적화
자유롭게 할당하고 지혜롭게 수거하라
이 표현은 “메모리는 마음껏 쓰되, 해제는 가비지 컬렉터가 깔끔하게 처리한다”는 철학을 반영합니다. 하지만 무한정 할당해도 된다는 뜻은 아닙니다. 할당은 자유롭지만, 수거를 고려한 패턴이 필요합니다.
for (int i = 0; i < 10000; i++)
{
var temp = new object();
}그러나 이처럼 과도한 단기 객체 생성을 반복하면 GC가 자주 발생하여 성능 저하로 이어질 수 있습니다.
세대별 가비지 컬렉션
.NET의 GC는 세대(Generation) 개념을 도입하여 효율성을 극대화합니다. 객체의 수명을 기준으로 분류됩니다:
- Generation 0: 새로 생성된 객체, 대부분 이 세대에서 소멸
- Generation 1: 0세대 컬렉션에서 살아남은 객체
- Generation 2: 장기적으로 살아남은 객체, 가장 큰 세대
- LOH (Large Object Heap): 일반적으로 85,000바이트 이상의 큰 객체를 위한 힙
GC.Collect(0); // Gen 0만 수집
GC.Collect(2); // 전체 수집세대 기반 수집은 최신 객체는 빨리 제거하고, 오래된 객체는 드물게 검사하여 성능을 개선합니다.
가비지 컬렉션을 이해했습니다. 우리는 뭘 해야 하죠?
가비지 컬렉터의 원리를 안다면, 개발자는 GC의 효율을 해치지 않도록 다음의 권고사항을 따를 필요가 있습니다.
객체를 너무 많이 할당하지 마세요
단기간에 많은 객체를 생성하면 GC가 자주 발생하게 되고, 이는 애플리케이션의 일시적인 지연을 유발할 수 있습니다.
// 반복 생성보다는 재사용
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
sb.Clear();
sb.Append("작업 중");
}너무 큰 객체 할당을 피하세요
85,000바이트 이상의 객체는 LOH에 저장되며, 이는 GC에 의해 수집되기 어려워 메모리 파편화를 유발할 수 있습니다.
// 되도록 배열 크기를 작게 나누고, 필요 시 버퍼 풀 사용 고려
byte[] buffer = new byte[100_000]; // LOH로 감너무 복잡한 참조 관계는 만들지 마세요
객체 간 순환 참조가 많을수록 GC가 객체 생존 여부를 판단하는 데 비용이 증가합니다.
// 순환 참조는 구조적으로 제거하거나 약한 참조(WeakReference) 사용 고려
class Node
{
public Node? Next;
public Node? Prev;
}루트를 너무 많이 만들지 마세요
GC는 GC 루트(예: 정적 변수, 스택 변수 등)에서 탐색을 시작합니다. 루트가 많을수록 탐색 비용이 증가합니다.
static object? globalObj; // 이런 정적 참조는 해제하지 않으면 GC 대상에서 제외됨작은 구멍이 댐을 무너뜨립니다
작은 메모리 누수가 반복되면 전체 애플리케이션 성능 저하로 이어질 수 있습니다. 이벤트 핸들러 등록 해제, IDisposable 구현 등은 필수적인 습관입니다.
class ResourceHolder : IDisposable
{
private bool disposed = false;
public void Dispose()
{
if (!disposed)
{
// 자원 해제
disposed = true;
}
}
}