[로봇활용_12주차] C# LOH(Large Object Heap)

최윤호·2025년 10월 29일
post-thumbnail

GC의 특별 관리 구역

가비지 컬렉터는 세대별 가비지 컬렉션을 사용해서 메모리를 효율적으로 청소합니다.
만약에 객체의 크기가 거대하다면 가비지 컬렉터는 어떻게 처리할까요?
이럴 때 .NET은 LOH(Large Object Heap)라고 하는 환경을 제공합니다.

1)LOH(Large Object Heap)란?

GC는 메모리를 효율적으로 관리하기 위해 객체의 크기에 따라 두 종류의 힙으로 나눕니다.
SOH(Small Object Heap): 작거나 중간 크기의 객체들이 저장되는 힙
LOH(Large Object Heap): 큰 객체들만 특별히 모아두는 힙

.NET에서 LOH 기준은 85,000 바이트 (약 83KB)입니다.
이 크기보다 큰 객체는 LOH의 관리 대상이 됩니다.

그렇다면 왜 굳이 이런 특별 구역을 만들었을까요?
그 이유는 바로 '복사 비용(Copying Cost)' 때문입니다.
GC는 메모리 단편화를 줄이기 위해 살아남은 객체를 다음 세대로 '복사'해야 합니다.
작은 객체들을 복사하는 것은 빠르고 효율적이죠. 하지만 85,000 바이트가 넘는
거대한 객체를 이리저리 복사하는 것은 엄청난 비용이 드는 작업입니다.
이러한 비효율을 막기 위해, GC는 "처음부터 크기가 큰 객체는 오래 사용될 가능성이 높으니,
굳이 0세대, 1세대를 거치게 하지 말고 바로 장기 보관 장소로 보내자!"라고 결정한 것입니다.

2)LOH의 독특한 특징들

LOH는 일반적인 힙과 다른 몇 가지 중요한 특징을 가집니다.

1. Gen 2로의 직행 티켓

LOH에 할당되는 객체는 0세대와 1세대를 건너뛰고, 논리적으로 2세대로 취급됩니다.
즉, 태어나는 순간부터 '원로' 대접받는 셈이죠.
이 때문에 LOH에 할당된 객체는 오직 Full GC가 발생할 때만 수집 대상이 됩니다.
거대 객체로 인한 잦은 가비지 컬렉션 발생을 막아 성능을 유지할 수 있습니다.

2. 메모리 파편화의 그늘

LOH의 가장 중요하고 주의해야 할 특징입니다.

  • 과거 (.NET Framework 4.5.1 이전): LOH는 메모리 압축을 수행하지 않았습니다.
  • 현재 (.NET Core 및 최신 .NET): 사용자가 명시적으로 요청할 때
    LOH에 대한 메모리 압축을 수행하지만, 여전히 비용이 큰 작업입니다.

거대한 객체들이 LOH에 할당되었다가 일부가 해제되면, 메모리 중간중간에
이가 빠진 것처럼 '구멍'이 생깁니다. 이를 메모리 파편화라고 합니다.
전체적인 여유 공간은 충분하더라도 우리가 새로 할당하려는 거대한 객체가 들어갈 만큼
'연속된 빈 공간'이 없어 OutOfMemoryException이 발생할 수 있습니다.

3. 코드로 직접 확인해 보기

간단한 코드를 통해 객체가 크기에 따라 어느 세대에 할당되는지 직접 확인해 볼 수 있습니다.

[코드]

class Program
{
    static void Main()
    {
        // LOH 기준(85,000 바이트)보다 작은 객체
        // 10,000 * 4 bytes(int) = 40,000 bytes
        int[] smallArray = new int[10000]; 
        Console.WriteLine($"작은 배열의 세대: Gen {GC.GetGeneration(smallArray)}");

        // LOH 기준보다 큰 객체
        // 30,000 * 4 bytes(int) = 120,000 bytes
        int[] largeArray = new int[30000];
        Console.WriteLine($"큰 배열의 세대: Gen {GC.GetGeneration(largeArray)}");
        
        // largeArray는 생성되자마자 Gen 2로 취급되는 것을 볼 수 있습니다.
        // (환경에 따라 다르게 표시될 수 있으나, 논리적으로는 Gen 2로 관리됩니다.)
    }
}

[실행 결과]

작은 배열의 세대: Gen 0
큰 배열의 세대: Gen 2

3)이제 무엇을 고려해야 할까?

LOH의 동작 방식을 이해했다면, 우리는 성능을 위해 다음을 고려할 수 있습니다.

  • 불필요한 거대 객체 생성을 피하세요: 특히 반복문 안에서 거대한 배열이나
    문자열을 계속 생성하고 해제하는 것은 메모리 파편화의 주범이 될 수 있습니다.
  • 객체 풀링(Object Pooling) 고려: byte[]버퍼처럼 크기가 크고 재사용이 잦은 객체는,
    미리 만들어두고 돌려쓰는 기법을 사용하면 LOH의 부하를 크게 줄일 수 있습니다.
  • 스트리밍(Streaming) 활용: 대용량 파일을 처리할 때, 파일 전체를 한 번에 올리는 대신,
    작은 청크(Chunk) 단위로 나누어 처리하는 것이 메모리 사용에 훨씬 효율적입니다.

4)LOH(Large Object Heap) 요약

우리는 이제 C#의 메모리 관리가 내부적으로 얼마나 정교한지 알아보았습니다.
이러한 내부 동작 원리를 이해하는 것은 성능 좋은 애플리케이션을 만드는 데 도움이 됩니다.

  • LOH는 85,000 바이트 이상의 거대 객체를 위한 별도의 힙 공간입니다.
  • LOH에 할당된 객체는 논리적으로 2세대로 취급됩니다.
  • LOH는 메모리 파편화에 취약할 수 있으며, 이는 개발자가 주의해야 할 부분입니다.
profile
🚀 미래의 엔지니어를 꿈꾸는 훈련생의 기록 📝

0개의 댓글