GC 성능 모니터링과 튜닝
애플리케이션의 성능을 최적화하기 위해서는 Garbage Collection(GC)의 동작을 모니터링하고 적절히 튜닝하는 것이 중요합니다. 이번 글에서는 메모리 프로파일링 도구를 소개하고, GC 성능을 모니터링하는 방법과 지표를 해석하는 방법을 알아보겠습니다. 또한 실전에서 GC를 튜닝하는 전략과 사례를 살펴보겠습니다.
메모리 프로파일링 도구 소개
Visual Studio Diagnostics Tools
Visual Studio는 강력한 성능 및 메모리 진단 도구를 제공합니다. 이를 통해 애플리케이션의 메모리 사용량과 GC 활동을 모니터링할 수 있습니다. 사용 방법:
- 디버깅 시작: Visual Studio에서 프로젝트를 열고, 디버깅 모드로 애플리케이션을 실행합니다.
- 진단 도구 열기:
디버깅 중에
Debug > Windows > Diagnostic Tools
메뉴를 선택하여 진단 도구 창을 엽니다. - 메모리 사용량 확인: 진단 도구 창에서 메모리 사용량 그래프와 GC 활동을 실시간으로 확인할 수 있습니다.
- 스냅샷 찍기: 특정 시점의 메모리 상태를 분석하기 위해 스냅샷을 찍을 수 있습니다.
dotMemory
JetBrains에서 제공하는 dotMemory는 전문적인 .NET 메모리 프로파일링 도구입니다. 메모리 누수, 객체 할당, GC 활동 등을 상세하게 분석할 수 있습니다. 주요 기능:
- 메모리 스냅샷 비교: 두 개의 메모리 스냅샷을 비교하여 메모리 사용량 변화를 분석합니다.
- 객체 경로 탐색: 특정 객체가 어떻게 생성되고 참조되는지 경로를 추적합니다.
- GC 이벤트 모니터링: GC의 수집 이벤트와 메모리 할당 정보를 확인합니다.
PerfView
PerfView는 Microsoft에서 제공하는 무료 성능 분석 도구로, GC 활동과 메모리 사용량을 상세하게 분석할 수 있습니다. 주요 기능:
- GC 힙 덤프 분석: 힙 메모리 상태를 덤프하여 객체 분포를 확인합니다.
- 이벤트 추적: GC 이벤트와 메모리 할당 정보를 추적합니다.
- CPU 성능 분석: CPU 사용량과 스레드 활동을 분석합니다.
GC 성능 모니터링 방법
성능 카운터 활용
Windows 성능 모니터를 사용하여 GC 관련 성능 카운터를 모니터링할 수 있습니다. 주요 카운터:
- % Time in GC: 애플리케이션 실행 시간 중 GC에 소비된 비율입니다.
- Gen 0 Collections: Gen 0 가비지 컬렉션 횟수입니다.
- Gen 1 Collections: Gen 1 가비지 컬렉션 횟수입니다.
- Gen 2 Collections: Gen 2 가비지 컬렉션 횟수입니다.
- Large Object Heap Size: LOH의 현재 크기입니다. 사용 방법:
- 성능 모니터 실행: Windows 키를 누르고 “성능 모니터"를 검색하여 실행합니다.
- 카운터 추가: 좌측 메뉴에서 “모니터링 도구 > 성능 모니터"를 선택하고, 그래프 영역을 우클릭하여 “카운터 추가"를 선택합니다.
- 카운터 선택: “.NET CLR Memory” 카테고리에서 원하는 카운터를 선택하고 애플리케이션 프로세스를 지정합니다.
코드 내에서 GC 정보 수집
GC
클래스를 사용하여 애플리케이션 내에서 GC 정보를 수집할 수 있습니다.
예시:
using System;
class Program
{
static void Main()
{
Console.WriteLine("총 메모리 할당: " + GC.GetTotalMemory(false) + " 바이트");
Console.WriteLine("Gen 0 수집 횟수: " + GC.CollectionCount(0));
Console.WriteLine("Gen 1 수집 횟수: " + GC.CollectionCount(1));
Console.WriteLine("Gen 2 수집 횟수: " + GC.CollectionCount(2));
}
}
지표 해석 방법
% Time in GC
- 높은 값: GC에 많은 시간이 소비되고 있음을 의미합니다. 메모리 할당이 과도하거나 객체 수명이 적절히 관리되지 않을 수 있습니다.
- 낮은 값: GC에 의한 성능 영향이 적습니다.
Gen 0, 1, 2 Collection Counts
- Gen 0 수집 횟수 많음: 짧은 수명의 객체가 많이 생성되고 있습니다.
- Gen 2 수집 횟수 많음: 오래된 객체가 많이 수집되고 있으며, 메모리 누수가 의심됩니다.
Large Object Heap Size
- 크기가 지속적으로 증가: LOH에 큰 객체가 계속 쌓이고 있으며, 메모리 파편화가 발생할 수 있습니다.
GC 튜닝을 위한 실전 전략과 사례
사례 1: 메모리 할당 최소화
문제점: 애플리케이션에서 메모리 할당이 과도하여 GC 빈도가 높아지고 성능이 저하됨. 해결책:
- 객체 재사용: 객체 풀링(Object Pooling)을 통해 빈번한 객체 생성을 피합니다.
- 값 타입 사용: 작은 데이터는 클래스 대신 구조체를 사용하여 힙 할당을 줄입니다.
Span<T>
활용: 메모리 복사를 최소화하여 할당을 줄입니다.
사례 2: LOH 파편화 해결
문제점: 큰 객체의 빈번한 생성과 해제로 LOH 파편화가 발생하여 메모리 사용량이 증가함. 해결책:
- 메모리 풀링:
ArrayPool<T>
를 사용하여 큰 배열을 재사용합니다. - 데이터 분할: 큰 데이터를 작은 청크로 분할하여 처리합니다.
- LOH 압축 활성화: 구성 파일에서 LOH 압축을 활성화합니다. 설정 예시:
<configuration>
<runtime>
<gcServer enabled="true"/>
<gcConcurrent enabled="true"/>
<GCLargeObjectHeapCompactionMode enabled="true"/>
</runtime>
</configuration>
사례 3: GC 모드 조정
문제점: 서버 애플리케이션에서 GC로 인한 일시 중단이 발생하여 응답 속도가 느려짐. 해결책:
- Server GC 활성화: 멀티코어 환경에서 Server GC를 사용하여 병렬로 가비지 컬렉션을 수행합니다.
- 백그라운드 수집 사용: 동시 수집을 활성화하여 애플리케이션 중단 시간을 줄입니다.
사례 4: 비동기 작업의 메모리 누수
문제점: 비동기 작업에서 이벤트 핸들러를 등록한 후 해제하지 않아 메모리 누수가 발생함. 해결책:
- 이벤트 핸들러 해제: 작업 완료 후 이벤트 핸들러를 반드시 해제합니다.
- 약한 이벤트 패턴 사용: 약한 참조를 사용하여 이벤트 핸들러를 등록합니다.
결론
GC 성능 모니터링과 튜닝은 애플리케이션의 안정성과 성능을 향상시키는 데 필수적입니다. 메모리 프로파일링 도구를 활용하여 메모리 사용량과 GC 활동을 모니터링하고, 지표를 해석하여 문제점을 파악할 수 있습니다. 실전 사례를 통해 적절한 튜닝 전략을 적용하면 GC로 인한 성능 저하를 효과적으로 개선할 수 있습니다.