가비지 컬렉터 핵심 지표
모니터링을 할 때 먼저 확인해야 할 데이터들이다. 이 지표들이 나쁘면 프로그램의 응답 속도가 급격히 떨어진다.
-
가비지 컬렉터 시간 점유율(% Time in GC): 전체 실행 시간 중 가비지 컬렉터가 차지하는 비중이다. 보통 5% 미만이 이상적이며, 20%를 넘어가면 심각한 메모리 할당 설계 오류가 있다고 판단한다.
-
초당 할당 바이트(Allcated Bytes/sec): 메모리가 소모되는 속도를 보여준다. 이 수치가 높으면 0세대 가비지 컬렉션이 자주 발생한다.
-
Gen 0/1/2 Collections: 각 세대별로 가비지 컬렉션이 몇 번 일어났는지 확인한다. 0세대는 많아도 괜찮지만, 2세대 횟수가 많다면 메모리 누수나 장기 생존 객체가 많다는 증거이다.
-
대형 객체 힙 크기(LOH Size): LOH는 압착이 잘 되지 않으므로 이 크기가 계속 커지면 메모리 파편화로 인해 OutOfMemoryException 예외가 발생할 수 있다.
가비지 컬렉터 모드
애플리케이션의 성격에 따라 런타임 설정을 통해 가비지 컬렉터의 엔진 자체를 교체할 수 있다.
-
워크 스테이션 모드: 일반적인 클라이언트 앱용으로 하나의 가비지 컬렉터 스레드만 사용하며, 메모리 사용량을 최소화하고 사용자 반응성에 집중한다.
-
서버 모드: 고성능 서버용으로 CPU 코어 당 하나씩 가비지 컬렉터 전용 힙과 스레드를 할당한다. 멀티 코어 환경에서 병렬로 가비지 컬렉터를 수행하므로 대량의 데이터를 처리할 때 처리량이 압도적이다. 다만 스레드와 힙이 많아지므로 메모리 점유율이 높아진다.
백그라운드 가비지 컬렉터
과거에는 가비지 컬렉터가 돌아가면 앱이 멈췄지만, 현대의 .NET은 이를 개선했다.
-
백그라운드 가비지 컬렉터: 2세대 수집 중에 0세대나 1세대 가비지 컬렉션이 필요하면, 2세대 수집을 잠시 멈추거나 병렬로 수행하여 하위 세대 수집을 먼저 끝낸다.
- 효과: 응답 없음 현상을 획기적으로 줄여준다. .NET 4.5 이후 서버 모드에서도 지원되며 현재 표준이다.
코드 레벨 튜닝
가장 좋은 튜닝은 가비지 컬렉터가 할 일을 만들지 않는 것이다.
-
객체 풀링(Object Pooling): 자주 생성되고 사라지는 객체를 new 키워드로 생성하지 않고, Pool에 적재한 뒤 재사용한다.
-
구조체 활용: 크기가 작고 수명이 짧은 데이터는 클래스 대신 구조체로 선언하여 스택에 할당되게 한다. 스택 메모리는 메소드 종료 시 즉시 사라지므로 가바지 컬렉터에 부담을 주지 않는다.
프로그래밍 방식 가비지 컬렉터
코드를 통해 가비지 컬렉터에 힌트를 주거나 동작을 제어할 수 있다.
-
GC.Collect() 메소드: 가비지 컬렉션을 강제로 실행하며, 일반적인 루프나 로직 안에서 호출하면 성능이 파괴된다. 이는 대규모 작업이 완전히 끝난 후, 혹은 시스템이 한가할 때 명시적으로 메모리를 비워야 할 때만 신중히 사용한다.
-
GCSettings.LatencyMode 속성: 실시간 작업 도중에 가비지 컬렉터가 끼어들지 못하도록 지연 모드를 설정할 수 있다.
- 속성 예시: Low Latency, SustainedLowLatency 등을 사용하여 특정 구간에서 가비지 컬렉터 수집을 억제한다.