이 문서에서는 가비지 수집 및 메모리 사용과 관련된 문제를 설명합니다. 관리되는 힙과 관련된 문제를 다루고 가비지 수집이 애플리케이션에 미치는 영향을 최소화하는 방법을 설명합니다. 각 문제에는 문제를 조사하는 데 사용할 수 있는 절차에 대한 링크가 있습니다.
성능 분석 도구
다음 섹션에서는 메모리 사용량 및 가비지 수집 문제를 조사하는 데 사용할 수 있는 도구에 대해 설명합니다. 이 문서의 뒷부분에서 제공하는 절차 는 이러한 도구를 참조합니다.
메모리 성능 카운터
성능 카운터를 사용하여 성능 데이터를 수집할 수 있습니다. 지침은 런타임 프로파일링을 참조하세요. .NET의 성능 카운터에 설명된 대로 성능 카운터의 .NET CLR 메모리 범주는 가비지 수집기 정보를 제공합니다.
SOS를 사용하여 디버깅
Windows 디버거(WinDbg)를 사용하여 관리되는 힙의 개체를 검사할 수 있습니다.
WinDbg를 설치하려면 Windows용 디버깅 도구 다운로드 페이지에서 Windows용 디버깅 도구를 설치합니다.
가비지 수집 ETW 이벤트
ETW(Windows용 이벤트 추적)는 .NET에서 제공하는 프로파일링 및 디버깅 지원을 보완하는 추적 시스템입니다. .NET Framework 4부터 가비지 수집 ETW 이벤트는 통계 관점에서 관리되는 힙을 분석하는 데 유용한 정보를 캡처합니다. 예를 들어 GCStart_V1
는 가비지 수집이 발생하려고 할 때 발생하는 이벤트로 다음과 같은 정보를 제공합니다.
- 어느 세대의 개체가 수집되고 있는지입니다.
- 가비지 수집을 유발한 원인입니다.
- 가비지 수집의 유형(동시 실행 또는 비동시 실행)입니다.
ETW 이벤트 로깅은 효율적이며 가비지 수집과 관련된 성능 문제를 마스킹하지 않습니다. 프로세스는 ETW 이벤트와 함께 자체 이벤트를 제공할 수 있습니다. 로깅할 때 애플리케이션의 이벤트와 가비지 수집 이벤트를 상호 연결하여 힙 문제가 발생하는 방법과 시기를 결정할 수 있습니다. 예를 들어 서버 애플리케이션은 클라이언트 요청의 시작과 끝에 이벤트를 제공할 수 있습니다.
프로파일링 API
CLR(공용 언어 런타임) 프로파일링 인터페이스는 가비지 수집 중에 영향을 받은 개체에 대한 자세한 정보를 제공합니다. 가비지 수집이 시작되고 끝날 때 프로파일러에 알릴 수 있습니다. 각 세대의 개체 식별을 포함하여 관리되는 힙의 개체에 대한 보고서를 제공할 수 있습니다. 자세한 내용은 프로파일링 개요를 참조하세요.
프로파일러가 포괄적인 정보를 제공할 수 있습니다. 그러나 복잡한 프로파일러가 애플리케이션의 동작을 수정할 수 있습니다.
애플리케이션 도메인 리소스 모니터링
.NET Framework 4부터 ARM(애플리케이션 도메인 리소스 모니터링)을 사용하면 호스트가 애플리케이션 도메인별로 CPU 및 메모리 사용량을 모니터링할 수 있습니다. 자세한 내용은 애플리케이션 도메인 리소스 모니터링을 참조하세요.
성능 문제 해결
첫 번째 단계는 문제가 실제로 가비지 수집인지 여부를 확인하는 것입니다. 문제가 확인되면 다음 목록에서 선택하여 문제를 해결합니다.
- 메모리 부족 예외가 throw됨
- 프로세스에서 너무 많은 메모리를 사용합니다.
- 가비지 수집기는 개체를 충분히 빠르게 회수하지 않습니다.
- 관리되는 힙이 너무 조각화되어 있습니다.
- 가비지 수집 일시 중지 시간이 너무 깁니다.
- 0세대가 너무 큽
- 가비지 수집 중 CPU 사용량이 너무 높습니다.
문제: 메모리 부족 예외가 발생함
관리 OutOfMemoryException 이(가) 발생할 수 있는 두 가지 합법적인 경우가 있습니다.
가상 메모리가 부족합니다.
가비지 수집기는 미리 결정된 크기의 세그먼트로 시스템에서 메모리를 할당합니다. 할당에 추가 세그먼트가 필요하지만 프로세스의 가상 메모리 공간에 연속된 사용 가능한 블록이 남아 있지 않으면 관리되는 힙에 대한 할당이 실패합니다.
할당할 실제 메모리가 부족합니다.
성능 검사 |
---|
메모리 부족 예외가 관리되는지 여부를 확인합니다. 예약할 수 있는 가상 메모리의 양을 결정합니다. 실제 메모리가 충분한지 확인합니다. |
예외가 합법적이지 않다고 판단되면 다음 정보를 사용하여 Microsoft 고객 서비스 및 지원에 문의하세요.
- 관리되는 메모리 부족 예외가 있는 스택입니다.
- 전체 메모리 덤프입니다.
- 가상 또는 실제 메모리가 문제가 되지 않음을 보여 주는 데이터를 포함하여 합법적인 메모리 부족 예외가 아님을 증명하는 데이터입니다.
문제: 프로세스가 너무 많은 메모리를 사용합니다.
일반적인 가정은 Windows 작업 관리자의 성능 탭에 표시되는 메모리 사용량이 너무 많은 메모리가 사용되는 시기를 나타낼 수 있다는 것입니다. 그러나 이 표시는 작업 집합과 관련이 있습니다. 가상 메모리 사용량에 대한 정보는 제공하지 않습니다.
문제가 관리되는 힙으로 인해 발생하는 것으로 확인되면 시간이 지남에 따라 관리되는 힙을 측정하여 패턴을 확인해야 합니다.
관리되는 힙으로 인해 문제가 발생하지 않는 경우 네이티브 디버깅을 사용해야 합니다.
성능 검사 |
---|
예약할 수 있는 가상 메모리의 양을 결정합니다. 관리되는 힙이 커밋하는 메모리 양을 결정합니다. 관리되는 힙이 예약하는 메모리 양을 결정합니다. 2세대에서 큰 개체를 식별합니다. 개체에 대한 참조를 확인합니다. |
문제: 가비지 수집기가 개체를 충분히 빠르게 회수하지 않음
개체가 가비지 수집에 대해 예상대로 회수되지 않는 것처럼 표시되면 해당 개체에 대한 강력한 참조가 있는지 확인해야 합니다.
데드 객체를 포함하는 세대에 대한 가비지 수집이 이루어지지 않은 경우에도 이 문제가 발생할 수 있습니다. 이는 데드 객체의 소멸자가 실행되지 않았음을 나타냅니다. 예를 들어 단일 스레드 아파트(STA) 애플리케이션을 실행할 때, 종료자 큐를 처리하는 스레드가 호출할 수 없는 경우에 가능합니다.
성능 검사 |
---|
개체에 대한 참조를 확인합니다. 파이널라이저가 실행되었는지 여부를 확인합니다. 완료 대기 중인 개체가 있는지 여부를 확인합니다. |
문제: 관리되는 힙이 너무 조각화되었습니다.
조각화 수준은 생성에 할당된 총 메모리에 대한 여유 공간의 비율로 계산됩니다. 2세대의 경우 허용되는 조각화 수준은 20개 이하의%. 2세대는 매우 커질 수 있으므로 조각화 비율이 절대값보다 더 중요합니다.
0세대에 여유 공간이 많은 것은 새 개체가 할당되는 세대이기 때문에 문제가 되지 않습니다.
조각화는 압축되지 않으므로 큰 개체 힙에서 항상 발생합니다. 인접한 자유 개체는 자연스럽게 단일 공간으로 축소되어 큰 개체 할당 요청을 충족합니다.
조각화는 1세대와 2세대에서 문제로 작용할 수 있습니다. 이러한 세대에 가비지 수집 후 사용 가능한 공간이 많은 경우 애플리케이션의 개체 사용을 수정해야 할 수 있으며 장기 개체의 수명을 다시 평가하는 것이 좋습니다.
개체를 과도하게 고정하면 조각화가 증가할 수 있습니다. 조각화가 높으면 너무 많은 개체가 고정되었을 수 있습니다.
가상 메모리의 조각화로 인해 가비지 수집기가 세그먼트를 추가하지 못하는 경우 원인은 다음 중 하나일 수 있습니다.
많은 작은 어셈블리를 자주 로드하고 언로드합니다.
관리되지 않는 코드와 상호 운용할 때 COM 개체에 대한 참조가 너무 많습니다.
큰 임시 개체를 생성하면 큰 개체 힙이 자주 힙 세그먼트를 할당하고 해제하게 됩니다.
CLR을 호스팅할 때 애플리케이션은 가비지 수집기에서 해당 세그먼트를 유지되도록 요청할 수 있습니다. 이렇게 하면 세그먼트 할당 빈도가 줄어듭니다. 이 작업은 STARTUP_FLAGS 열거형에서 STARTUP_HOARD_GC_VM 플래그를 사용하여 수행됩니다.
성능 검사 |
---|
관리되는 힙의 여유 공간 크기를 결정합니다. 고정된 개체의 수를 확인합니다. |
조각화에 대한 정당한 원인이 없다고 생각되면 Microsoft 고객 서비스 및 지원에 문의하세요.
문제: 가비지 수집 일시 중지 시간이 너무 깁니다.
가비지 수집은 소프트 리얼타임에서 작동하므로 애플리케이션에서 일부 일시 중지를 허용할 수 있어야 합니다. 소프트 실시간 기준은 작업의 95% 정시에 완료되어야 한다는 것입니다.
동시 가비지 수집에서 관리되는 스레드는 컬렉션 중에 실행될 수 있습니다. 즉, 일시 중지가 매우 최소화됩니다.
임시 가비지 수집(0세대 및 1세대)은 몇 밀리초만 지속되므로 일시 중지를 줄이는 것은 일반적으로 불가능합니다. 그러나 애플리케이션의 할당 요청 패턴을 변경하여 2세대 컬렉션의 일시 중지를 줄일 수 있습니다.
또 다른 더 정확한 방법은 가비지 수집 ETW 이벤트를 사용하는 것입니다. 이벤트 시퀀스의 타임스탬프 차이를 더하여 수집 시간표를 찾을 수 있습니다. 전체 컬렉션 시퀀스에는 실행 엔진의 일시 중단, 가비지 수집 자체 및 실행 엔진의 재개가 포함됩니다.
가비지 수집 알림을 사용하여 서버에 2세대 컬렉션이 있는지 여부와 요청을 다른 서버로 다시 라우팅하면 일시 중지 문제를 완화할 수 있는지 여부를 확인할 수 있습니다.
성능 검사 |
---|
가비지 수집의 시간 길이를 결정합니다. 가비지 수집의 원인을 확인합니다. |
문제: 0세대가 너무 큽
0세대는 특히 워크스테이션 가비지 수집 대신 서버 가비지 수집을 사용하는 경우 64비트 시스템에 더 많은 수의 개체가 있을 수 있습니다. 이는 이러한 환경에서 0세대 가비지 수집을 트리거하는 임계값이 더 높고 0세대 컬렉션이 훨씬 커질 수 있기 때문입니다. 가비지 수집이 트리거되기 전에 애플리케이션이 더 많은 메모리를 할당하면 성능이 향상됩니다.
문제: 가비지 수집 중 CPU 사용량이 너무 높습니다.
가비지 수집 중에 CPU 사용량이 높아질 것입니다. 가비지 수집에 상당한 양의 프로세스 시간이 소요되는 경우 컬렉션 수가 너무 빈번하거나 컬렉션이 너무 오래 지속됩니다. 관리되는 힙에서 개체의 할당 속도가 증가하면 가비지 수집이 더 자주 발생합니다. 할당 속도를 줄이면 가비지 수집 빈도가 줄어듭니다.
Allocated Bytes/second
성능 카운터를 사용하여 할당 비율을 모니터링할 수 있습니다. 자세한 내용은 .NET의 성능 카운터를 참조하세요.
컬렉션의 기간은 주로 할당 후에 유지되는 개체 수의 요소입니다. 수집할 개체가 많은 경우 가비지 수집기는 대량의 메모리를 처리해야 합니다. 생존자를 압축하는 작업은 시간이 많이 걸립니다. 컬렉션 중에 처리된 개체 수를 확인하려면 지정된 세대에 대해 가비지 수집의 끝에 있는 디버거에서 중단점을 설정합니다.
성능 검사 |
---|
높은 CPU 사용량이 가비지 수집으로 인해 발생하는지 확인합니다. 가비지 수집의 끝에 중단점을 설정합니다. |
문제 해결 지침
이 섹션에서는 조사를 시작할 때 고려해야 하는 지침을 설명합니다.
워크스테이션 또는 서버 가비지 컬렉션
올바른 유형의 가비지 수집을 사용하고 있는지 확인합니다. 애플리케이션에서 여러 스레드 및 개체 인스턴스를 사용하는 경우 워크스테이션 가비지 수집 대신 서버 가비지 수집을 사용합니다. 서버 가비지 수집은 여러 스레드에서 작동하는 반면 워크스테이션 가비지 수집에는 애플리케이션의 여러 인스턴스가 자체 가비지 수집 스레드를 실행하고 CPU 시간 동안 경쟁해야 합니다.
부하가 낮고 백그라운드에서 자주 수행하지 않는 애플리케이션(예: 서비스)은 동시 가비지 수집을 사용하지 않도록 설정된 워크스테이션 가비지 수집을 사용할 수 있습니다.
관리된 힙 크기를 언제 측정해야 하는지
프로파일러를 사용하지 않는 한 성능 문제를 효과적으로 진단하기 위해 일관된 측정 패턴을 설정해야 합니다. 일정을 설정하려면 다음 사항을 고려하세요.
- 2세대 가비지 수집 후에 측정하면 관리되는 전체 힙에 가비지(데드 개체)가 없습니다.
- 0세대 가비지 수집 직후 측정하면 1세대와 2세대의 개체는 아직 수집되지 않습니다.
- 가비지 수집이 시작되기 직전에 측정하면, 가비지 수집이 시작되기 전에 가능한 많은 할당을 측정할 수 있습니다.
- 가비지 수집기 데이터 구조가 통과에 유효한 상태가 아니고 전체 결과를 제공하지 못할 수 있으므로 가비지 수집 중 측정은 문제가 됩니다. 이것은 의도적인 것입니다.
- 동시 가비지 수집과 함께 워크스테이션 가비지 수집을 사용하는 경우, 회수된 개체는 압축되지 않으므로 힙 크기가 동일하거나 더 커질 수 있습니다. 조각화로 인해 힙이 더 큰 것처럼 보일 수 있습니다.
- 실제 메모리 로드가 너무 높으면 두 번째 세대에 대한 동시 가비지 수집이 지연됩니다.
다음 절차에서는 관리되는 힙을 측정할 수 있도록 중단점을 설정하는 방법을 설명합니다.
가비지 수집의 끝에 중단점을 설정하려면
SOS 디버거 확장이 로드된 WinDbg에서 다음 명령을 입력합니다.
bp mscorwks!WKS::GCHeap::RestartEE "j (dwo(mscorwks!WKS::GCHeap::GcCondemnedGeneration)==2) 'kb';'g'"
GcCondemnedGeneration
를 원하는 세대로 설정하세요. 이 명령에는 프라이빗 기호가 필요합니다.이 명령은 2세대 개체가 가비지 수집을 위해 회수된 후 실행되는 경우
RestartEE
중단을 강제합니다.서버 가비지 수집에서는 스레드가 하나만 호출
RestartEE
되므로 중단점은 2세대 가비지 수집 중에 한 번만 발생합니다.
성능 검사 절차
이 섹션에서는 성능 문제의 원인을 격리하기 위한 다음 절차를 설명합니다.
- 가비지 수집으로 인해 문제가 발생하는지 여부를 확인합니다.
- 메모리 부족 예외가 관리되는지 여부를 확인합니다.
- 예약할 수 있는 가상 메모리의 양을 결정합니다.
- 실제 메모리가 충분한지 확인합니다.
- 관리되는 힙이 커밋하는 메모리 양을 결정합니다.
- 관리되는 힙이 예약하는 메모리 양을 결정합니다.
- 2세대에서 큰 개체를 식별합니다.
- 개체에 대한 참조를 확인합니다.
- 파이널라이저가 실행되었는지 여부를 확인합니다.
- 완료 대기 중인 개체가 있는지 여부를 확인합니다.
- 관리되는 힙의 여유 공간 크기를 결정합니다.
- 고정된 개체의 수를 확인합니다.
- 가비지 수집의 시간 길이를 결정합니다.
- 가비지 수집을 트리거한 내용을 확인합니다.
- 높은 CPU 사용량이 가비지 수집으로 인해 발생하는지 여부를 확인합니다.
가비지 수집으로 인해 문제가 발생하는지 확인하려면
다음 두 메모리 성능 카운터를 검사합니다.
% GC에서의 시간. 마지막 가비지 수집 주기 이후 가비지 수집을 수행하는 데 소요된 경과된 시간의 백분율을 표시합니다. 이 카운터를 사용하여 가비지 수집기가 관리 힙 공간을 확보하기 위해 너무 많은 시간을 소비하고 있는지 확인하십시오. 가비지 수집에 소요된 시간이 상대적으로 낮으면 관리되는 힙 외부의 리소스 문제를 나타낼 수 있습니다. 동시 또는 백그라운드 가비지 컬렉션이 관련될 경우, 이 카운터는 정확하지 않을 수 있습니다.
# 커밋된 총 바이트 수입니다. 가비지 수집기가 현재 커밋한 가상 메모리의 양을 표시합니다. 이 카운터를 사용하여 가비지 수집기에서 사용하는 메모리가 애플리케이션에서 사용하는 메모리의 과도한 부분인지 여부를 확인합니다.
대부분의 메모리 성능 카운터는 가비지 수집이 끝날 때마다 업데이트됩니다. 따라서 정보를 원하는 현재 조건을 반영하지 않을 수 있습니다.
메모리 부족 예외가 관리되는지 여부를 확인하려면
SOS 디버거 확장이 로드된 WinDbg 또는 Visual Studio 디버거에서 인쇄 예외(
pe
) 명령을 입력합니다.!pe
예외가 관리 OutOfMemoryException 되는 경우 다음 예제와 같이 예외 유형으로 표시됩니다.
Exception object: 39594518 Exception type: System.OutOfMemoryException Message: <none> InnerException: <none> StackTrace (generated):
출력에서 예외를 지정하지 않는 경우 메모리 부족 예외가 발생한 스레드를 결정해야 합니다. 디버거에 다음 명령을 입력하여 호출 스택이 있는 모든 스레드를 표시합니다.
~\*kb
예외 호출이 있는 스택이 있는 스레드는 인수로
RaiseTheException
표시됩니다. 관리되는 예외 개체입니다.28adfb44 7923918f 5b61f2b4 00000000 5b61f2b4 mscorwks!RaiseTheException+0xa0
다음 명령을 사용하여 중첩된 예외를 덤프할 수 있습니다.
!pe -nested
예외를 찾을 수 없는 경우 메모리 부족 예외는 관리되지 않는 코드에서 발생했습니다.
예약할 수 있는 가상 메모리의 양을 확인하려면
SOS 디버거 확장이 로드된 WinDbg에서 다음 명령을 입력하여 가장 큰 무료 지역을 가져옵니다.
!address -summary
다음 출력과 같이 가장 큰 사용 가능한 지역이 표시됩니다.
Largest free region: Base 54000000 - Size 0003A980
이 예제에서 가장 큰 자유 영역의 크기는 약 24000KB(16진수 3A980)입니다. 이 지역은 가비지 수집기가 분할에 필요로 하는 크기보다 훨씬 작습니다.
또는
vmstat
명령을 사용합니다.!vmstat
사용 가능한 가장 큰 지역은 다음 출력에서 볼 수 있듯이 MAXIMUM 열의 최대 값입니다.
TYPE MINIMUM MAXIMUM AVERAGE BLK COUNT TOTAL ~~~~ ~~~~~~~ ~~~~~~~ ~~~~~~~ ~~~~~~~~~~ ~~~~ Free: Small 8K 64K 46K 36 1,671K Medium 80K 864K 349K 3 1,047K Large 1,384K 1,278,848K 151,834K 12 1,822,015K Summary 8K 1,278,848K 35,779K 51 1,824,735K
실제 메모리가 충분한지 확인하려면
Windows 작업 관리자를 시작합니다.
탭에서
Performance
커밋된 값을 확인합니다. (Windows 7에서는Commit (KB)
의System group
을 확인하십시오.)Total
가Limit
에 가까운 경우, 실제 메모리가 부족합니다.
관리되는 힙이 커밋하는 메모리 양을 확인하려면
메모리 성능 카운터를
# Total committed bytes
사용하여 관리되는 힙이 커밋하는 바이트 수를 가져옵니다. 가비지 수집기는 필요에 따라 세그먼트에 청크를 커밋하며, 모든 청크를 동시에 커밋하지 않습니다.비고
관리되는 힙의
# Bytes in all Heaps
실제 메모리 사용량을 나타내지 않으므로 성능 카운터를 사용하지 마세요. 세대의 크기는 이 값에 포함되며, 실제로 이는 임계 크기입니다. 즉, 세대가 객체로 가득 찼을 때 가비지 수집을 유발하는 크기를 의미합니다. 따라서 이 값은 일반적으로 0입니다.
관리되는 힙이 예약하는 메모리 양을 확인하려면
메모리 성능 카운터를
# Total reserved bytes
사용합니다.가비지 수집기는 세그먼트에 메모리를 예약하며, 명령을 사용하여
eeheap
세그먼트가 시작되는 위치를 확인할 수 있습니다.중요합니다
가비지 수집기가 각 세그먼트에 할당하는 메모리 양을 확인할 수 있지만 세그먼트 크기는 구현에 따라 달라지며 정기적인 업데이트를 포함하여 언제든지 변경될 수 있습니다. 앱에서 특정 세그먼트 크기를 가정하거나 의존해서는 안 되며, 세그먼트 할당에 사용할 수 있는 메모리 크기를 구성하려고 해서도 안 됩니다.
SOS 디버거 확장이 로드된 WinDbg 또는 Visual Studio 디버거에서 다음 명령을 입력합니다.
!eeheap -gc
결과는 다음과 같습니다.
Number of GC Heaps: 2 ------------------------------ Heap 0 (002db550) generation 0 starts at 0x02abe29c generation 1 starts at 0x02abdd08 generation 2 starts at 0x02ab0038 ephemeral segment allocation context: none segment begin allocated size 02ab0000 02ab0038 02aceff4 0x0001efbc(126908) Large object heap starts at 0x0aab0038 segment begin allocated size 0aab0000 0aab0038 0aab2278 0x00002240(8768) Heap Size 0x211fc(135676) ------------------------------ Heap 1 (002dc958) generation 0 starts at 0x06ab1bd8 generation 1 starts at 0x06ab1bcc generation 2 starts at 0x06ab0038 ephemeral segment allocation context: none segment begin allocated size 06ab0000 06ab0038 06ab3be4 0x00003bac(15276) Large object heap starts at 0x0cab0038 segment begin allocated size 0cab0000 0cab0038 0cab0048 0x00000010(16) Heap Size 0x3bbc(15292) ------------------------------ GC Heap Size 0x24db8(150968)
"세그먼트"로 표시된 주소는 세그먼트의 시작 주소입니다.
2세대에서 큰 개체를 확인하려면
SOS 디버거 확장이 로드된 WinDbg 또는 Visual Studio 디버거에서 다음 명령을 입력합니다.
!dumpheap –stat
관리되는 힙이 큰 경우
dumpheap
를 완료하는 데 시간이 걸릴 수 있습니다.출력의 마지막 몇 줄에서 분석을 시작할 수 있습니다. 가장 많은 공간을 사용하는 개체가 나열되기 때문입니다. 다음은 그 예입니다.
2c6108d4 173712 14591808 DevExpress.XtraGrid.Views.Grid.ViewInfo.GridCellInfo 00155f80 533 15216804 Free 7a747c78 791070 15821400 System.Collections.Specialized.ListDictionary+DictionaryNode 7a747bac 700930 19626040 System.Collections.Specialized.ListDictionary 2c64e36c 78644 20762016 DevExpress.XtraEditors.ViewInfo.TextEditViewInfo 79124228 121143 29064120 System.Object[] 035f0ee4 81626 35588936 Toolkit.TlkOrder 00fcae40 6193 44911636 WaveBasedStrategy.Tick_Snap[] 791242ec 40182 90664128 System.Collections.Hashtable+bucket[] 790fa3e0 3154024 137881448 System.String Total 8454945 objects
나열된 마지막 개체는 문자열이며 가장 많은 공간을 차지합니다. 애플리케이션을 검사하여 문자열 개체를 최적화하는 방법을 확인할 수 있습니다. 150바이트에서 200바이트 사이의 문자열을 보려면 다음을 입력합니다.
!dumpheap -type System.String -min 150 -max 200
결과의 예는 다음과 같습니다.
Address MT Size Gen 1875d2c0 790fa3e0 152 2 System.String HighlightNullStyle_Blotter_PendingOrder-11_Blotter_PendingOrder-11 …
ID에 문자열 대신 정수 사용이 더 효율적일 수 있습니다. 동일한 문자열이 수천 번 반복되는 경우 문자열 인턴링을 고려합니다. 문자열 인턴링에 대한 자세한 내용은 메서드에 대한 String.Intern 참조 항목을 참조하세요.
개체에 대한 참조를 확인하려면
SOS 디버거 확장이 로드된 WinDbg에서 다음 명령을 입력하여 개체에 대한 참조를 나열합니다.
!gcroot
또는
특정 개체에 대한 참조를 확인하려면 주소를 포함합니다.
!gcroot 1c37b2ac
스택에 있는 뿌리는 거짓 양성일 수 있습니다. 자세한 내용을 보려면
!help gcroot
명령을 사용하세요.ebx:Root:19011c5c(System.Windows.Forms.Application+ThreadContext)-> 19010b78(DemoApp.FormDemoApp)-> 19011158(System.Windows.Forms.PropertyStore)-> … [omitted] 1c3745ec(System.Data.DataTable)-> 1c3747a8(System.Data.DataColumnCollection)-> 1c3747f8(System.Collections.Hashtable)-> 1c376590(System.Collections.Hashtable+bucket[])-> 1c376c98(System.Data.DataColumn)-> 1c37b270(System.Data.Common.DoubleStorage)-> 1c37b2ac(System.Double[]) Scan Thread 0 OSTHread 99c Scan Thread 6 OSTHread 484
gcroot
명령을 완료하는 데 시간이 오래 걸릴 수 있습니다. 가비지 수집에서 회수되지 않는 개체는 라이브 개체입니다. 즉, 일부 루트는 개체에 직접 또는 간접적으로 유지되므로gcroot
개체에 경로 정보를 반환해야 합니다. 반환된 그래프를 검사하고 이러한 개체가 여전히 참조되는 이유를 확인해야 합니다.
종료자가 실행되었는지 여부를 확인하려면
다음 코드가 포함된 테스트 프로그램을 실행합니다.
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
테스트에서 문제를 해결하면 해당 개체의 종료자가 일시 중단되었기 때문에 가비지 수집기가 개체를 회수하지 않았다는 의미입니다. 이 GC.WaitForPendingFinalizers 메서드를 사용하면 종료자가 작업을 완료하고 문제를 해결할 수 있습니다.
완료 대기 중인 개체가 있는지 여부를 확인하려면
SOS 디버거 확장이 로드된 WinDbg 또는 Visual Studio 디버거에서 다음 명령을 입력합니다.
!finalizequeue
완료할 준비가 된 개체의 수를 확인합니다. 숫자가 높은 경우 이러한 종료자가 전혀 진행되지 않거나 충분히 빠르게 진행될 수 없는 이유를 검사해야 합니다.
스레드의 출력을 얻으려면 다음 명령을 입력합니다.
!threads -special
이 명령은 다음과 같은 출력을 제공합니다.
OSID Special thread type 2 cd0 DbgHelper 3 c18 Finalizer 4 df0 GC SuspendEE
종료자 스레드는 현재 실행 중인 종료자(있는 경우)를 나타냅니다. 종료자 스레드가 종료자를 실행하지 않는 경우 이벤트가 해당 작업을 수행하도록 지시할 때까지 기다리고 있습니다. 대부분의 경우, 종료자 스레드는 THREAD_HIGHEST_PRIORITY에서 실행되기 때문에 이 상태로 표시됩니다. 실행할 종료자가 있으면 종료자 실행을 매우 빠르게 완료해야 합니다.
관리되는 힙의 여유 공간 양을 확인하려면
SOS 디버거 확장이 로드된 WinDbg 또는 Visual Studio 디버거에서 다음 명령을 입력합니다.
!dumpheap -type Free -stat
이 명령은 다음 예제와 같이 관리되는 힙에 있는 모든 사용 가능한 개체의 총 크기를 표시합니다.
total 230 objects Statistics: MT Count TotalSize Class Name 00152b18 230 40958584 Free Total 230 objects
0세대에서 사용 가능한 공간을 확인하려면 다음 명령을 입력하여 세대별 메모리 사용량 정보를 입력합니다.
!eeheap -gc
이 명령은 다음과 유사한 출력을 표시합니다. 마지막 줄에는 임시 세그먼트가 표시됩니다.
Heap 0 (0015ad08) generation 0 starts at 0x49521f8c generation 1 starts at 0x494d7f64 generation 2 starts at 0x007f0038 ephemeral segment allocation context: none segment begin allocated size 00178250 7a80d84c 7a82f1cc 0x00021980(137600) 00161918 78c50e40 78c7056c 0x0001f72c(128812) 007f0000 007f0038 047eed28 0x03ffecf0(67103984) 3a120000 3a120038 3a3e84f8 0x002c84c0(2917568) 46120000 46120038 49e05d04 0x03ce5ccc(63855820)
0세대에서 사용하는 공간을 계산합니다.
? 49e05d04-0x49521f8c
결과는 다음과 같습니다. 0세대는 약 9MB입니다.
Evaluate expression: 9321848 = 008e3d78
다음 명령은 0세대 범위 내에서 사용 가능한 공간을 덤프합니다.
!dumpheap -type Free -stat 0x49521f8c 49e05d04
결과는 다음과 같습니다.
------------------------------ Heap 0 total 409 objects ------------------------------ Heap 1 total 0 objects ------------------------------ Heap 2 total 0 objects ------------------------------ Heap 3 total 0 objects ------------------------------ total 409 objects Statistics: MT Count TotalSize Class Name 0015a498 409 7296540 Free Total 409 objects
이 출력은 힙의 Generation 0 부분이 개체에 9MB의 공간을 차지하고 있으며 7MB의 메모리 공간이 비어 있음을 보여줍니다. 이 분석은 0세대가 조각화에 기여하는 정도를 보여 줍니다. 이 힙 사용량은 장기 객체로 인해 발생하는 조각화의 원인으로, 총 사용량에서 차감되어야 합니다.
고정된 객체의 수를 확인하려면
SOS 디버거 확장이 로드된 WinDbg 또는 Visual Studio 디버거에서 다음 명령을 입력합니다.
!gchandles
화면에 표시되는 통계에는 다음 예제와 같이 고정된 핸들의 수가 포함됩니다.
GC Handle Statistics: Strong Handles: 29 Pinned Handles: 10
가비지 수집의 시간 길이를 확인하려면
% Time in GC
메모리 성능 카운터를 검사합니다.이 값은 샘플 간격 시간을 사용하여 계산됩니다. 카운터는 각 가비지 수집의 끝에서 업데이트되므로 간격 동안 컬렉션이 발생하지 않은 경우 현재 샘플은 이전 샘플과 동일한 값을 갖습니다.
컬렉션 시간은 샘플 간격 시간을 백분율 값과 곱하여 가져옵니다.
다음 데이터는 8초 연구에 대해 2초의 4개의 샘플링 간격을 보여 줍니다.
Gen0
,Gen1
, 및Gen2
열에는 해당 세대에 대한 가비지 수집이 간격이 끝날 때까지 완료된 총 횟수가 표시됩니다.Interval Gen0 Gen1 Gen2 % Time in GC 1 9 3 1 10 2 10 3 1 1 3 11 3 1 3 4 11 3 1 3
이 정보는 가비지 수집이 발생한 시기를 표시하지 않지만 시간 간격으로 발생한 가비지 수집 수를 확인할 수 있습니다. 최악의 경우를 가정하면 10세대 0 가비지 수집은 두 번째 간격의 시작 부분에 완료되고 11세대 0 가비지 수집은 세 번째 간격의 끝에 완료됩니다. 10번째 가비지 수집의 끝과 11번째 가비지 수집의 끝 사이의 시간은 약 2초이고 성능 카운터는 3개의%표시하므로 11세대 0 가비지 수집의 기간은 (2초 * 3% = 60ms)였습니다.
다음 예제에서는 5개의 간격이 있습니다.
Interval Gen0 Gen1 Gen2 % Time in GC 1 9 3 1 3 2 10 3 1 1 3 11 4 1 1 4 11 4 1 1 5 11 4 2 20
2세대 2 가비지 수집은 네 번째 간격 동안 시작되어 다섯 번째 간격으로 완료되었습니다. 최악의 경우를 가정할 때 마지막 가비지 수집은 세 번째 간격의 시작 부분에 완료된 0세대 컬렉션에 대한 것이었고, 2세대 가비지 수집은 다섯 번째 간격의 끝에 완료되었습니다. 따라서 0세대 가비지 수집의 끝과 2세대 가비지 수집의 끝 사이의 시간은 4초입니다.
% Time in GC
카운터는%20이므로 2세대 가비지 수집이 수행할 수 있는 최대 시간은 (4초 * 20% = 800ms)입니다.또는 가비지 수집 ETW 이벤트를 사용하여 가비지 수집의 길이를 확인하고 정보를 분석하여 가비지 수집 기간을 확인할 수 있습니다.
예를 들어 다음 데이터는 비동기 가비지 수집 중에 발생한 이벤트 시퀀스를 보여줍니다.
Timestamp Event name 513052 GCSuspendEEBegin_V1 513078 GCSuspendEEEnd 513090 GCStart_V1 517890 GCEnd_V1 517894 GCHeapStats 517897 GCRestartEEBegin 517918 GCRestartEEEnd
관리되는 스레드를 일시 중단하는 데 26us(
GCSuspendEEEnd
–GCSuspendEEBegin_V1
)가 걸렸습니다.실제 가비지 수집에는 4.8ms(
GCEnd_V1
–GCStart_V1
)가 걸렸습니다.관리되는 스레드를 다시 열 때는 21us(
GCRestartEEEnd
–GCRestartEEBegin
)가 걸렸습니다.다음 출력은 백그라운드 가비지 수집에 대한 예제를 제공하며 프로세스, 스레드 및 이벤트 필드를 포함합니다. (모든 데이터가 표시되지는 않습니다.)
timestamp(us) event name process thread event field 42504385 GCSuspendEEBegin_V1 Test.exe 4372 1 42504648 GCSuspendEEEnd Test.exe 4372 42504816 GCStart_V1 Test.exe 4372 102019 42504907 GCStart_V1 Test.exe 4372 102020 42514170 GCEnd_V1 Test.exe 4372 42514204 GCHeapStats Test.exe 4372 102020 42832052 GCRestartEEBegin Test.exe 4372 42832136 GCRestartEEEnd Test.exe 4372 63685394 GCSuspendEEBegin_V1 Test.exe 4744 6 63686347 GCSuspendEEEnd Test.exe 4744 63784294 GCRestartEEBegin Test.exe 4744 63784407 GCRestartEEEnd Test.exe 4744 89931423 GCEnd_V1 Test.exe 4372 102019 89931464 GCHeapStats Test.exe 4372
GCStart_V1
42504816 이벤트는 마지막 필드가1
이기 때문에 백그라운드 가비지 수집임을 나타냅니다. 이제 이것은 가비지 수집 번호 102019가 됩니다.이
GCStart
이벤트는 백그라운드 가비지 수집을 시작하기 전에 임시 가비지 수집이 필요하기 때문에 발생합니다. 가비지 수집 번호 102020이 됩니다.42514170번 가비지 수집 번호 102020이 완료되었습니다. 관리되는 스레드는 이 시점에서 다시 시작됩니다. 이 작업은 이 백그라운드 가비지 수집을 트리거한 스레드 4372에서 완료됩니다.
스레드 4744에서 일시 중단이 발생합니다. 백그라운드 가비지 수집이 관리되는 스레드를 일시 중단해야 하는 유일한 경우입니다. 이 기간은 약 99ms((63784407-63685394)/1000)입니다.
GCEnd
백그라운드 가비지 수집의 이벤트는 89931423에 있습니다. 즉, 백그라운드 가비지 수집은 약 47초 동안 지속되었습니다((89931423-42504816)/1000).관리되는 스레드가 실행되는 동안 여러 차례의 순간적인 가비지 수집이 발생하는 것을 볼 수 있습니다.
가비지 수집을 유발한 원인을 확인하려면
SOS 디버거 확장이 로드된 WinDbg 또는 Visual Studio 디버거에서 다음 명령을 입력하여 호출 스택이 있는 모든 스레드를 표시합니다.
~*kb
이 명령은 다음과 유사한 출력을 표시합니다.
0012f3b0 79ff0bf8 mscorwks!WKS::GCHeap::GarbageCollect 0012f454 30002894 mscorwks!GCInterface::CollectGeneration+0xa4 0012f490 79fa22bd fragment_ni!request.Main(System.String[])+0x48
가비지 수집이 운영 체제의 메모리 부족 알림으로 인해 발생한 경우 스레드가 종료자 스레드라는 점을 제외하고 호출 스택은 유사합니다. 종료자 스레드는 비동기 저메모리 상태 알림을 받고 가비지 수집을 유도합니다.
가비지 수집이 메모리 할당으로 인해 발생한 경우 스택은 다음과 같이 표시됩니다.
0012f230 7a07c551 mscorwks!WKS::GCHeap::GarbageCollectGeneration 0012f2b8 7a07cba8 mscorwks!WKS::gc_heap::try_allocate_more_space+0x1a1 0012f2d4 7a07cefb mscorwks!WKS::gc_heap::allocate_more_space+0x18 0012f2f4 7a02a51b mscorwks!WKS::GCHeap::Alloc+0x4b 0012f310 7a02ae4c mscorwks!Alloc+0x60 0012f364 7a030e46 mscorwks!FastAllocatePrimitiveArray+0xbd 0012f424 300027f4 mscorwks!JIT_NewArr1+0x148 000af70f 3000299f fragment_ni!request..ctor(Int32, Single)+0x20c 0000002a 79fa22bd fragment_ni!request.Main(System.String[])+0x153
즉시 지원자(
JIT_New*
)가 결국GCHeap::GarbageCollectGeneration
을 호출합니다. 2세대 가비지 수집이 할당으로 인해 발생한다고 판단하는 경우 2세대 가비지 수집에서 수집되는 개체와 이를 방지하는 방법을 결정해야 합니다. 즉, 2세대 가비지 수집의 시작과 끝과 2세대 수집을 발생시킨 개체의 차이를 확인하려고 합니다.예를 들어 디버거에 다음 명령을 입력하여 2세대 컬렉션의 시작을 표시합니다.
!dumpheap –stat
예제 출력(가장 많은 공간을 사용하는 개체를 표시하도록 축소됨):
79124228 31857 9862328 System.Object[] 035f0384 25668 11601936 Toolkit.TlkPosition 00155f80 21248 12256296 Free 79103b6c 297003 13068132 System.Threading.ReaderWriterLock 7a747ad4 708732 14174640 System.Collections.Specialized.HybridDictionary 7a747c78 786498 15729960 System.Collections.Specialized.ListDictionary+DictionaryNode 7a747bac 700298 19608344 System.Collections.Specialized.ListDictionary 035f0ee4 89192 38887712 Toolkit.TlkOrder 00fcae40 6193 44911636 WaveBasedStrategy.Tick_Snap[] 7912c444 91616 71887080 System.Double[] 791242ec 32451 82462728 System.Collections.Hashtable+bucket[] 790fa3e0 2459154 112128436 System.String Total 6471774 objects
2세대가 끝날 때 명령을 반복합니다.
!dumpheap –stat
예제 출력(가장 많은 공간을 사용하는 개체를 표시하도록 축소됨):
79124228 26648 9314256 System.Object[] 035f0384 25668 11601936 Toolkit.TlkPosition 79103b6c 296770 13057880 System.Threading.ReaderWriterLock 7a747ad4 708730 14174600 System.Collections.Specialized.HybridDictionary 7a747c78 786497 15729940 System.Collections.Specialized.ListDictionary+DictionaryNode 7a747bac 700298 19608344 System.Collections.Specialized.ListDictionary 00155f80 13806 34007212 Free 035f0ee4 89187 38885532 Toolkit.TlkOrder 00fcae40 6193 44911636 WaveBasedStrategy.Tick_Snap[] 791242ec 32370 82359768 System.Collections.Hashtable+bucket[] 790fa3e0 2440020 111341808 System.String Total 6417525 objects
출력의 끝에서
double[]
개체가 사라졌습니다. 이는 개체가 수집되었음을 의미합니다. 이러한 개체는 약 70MB를 차지합니다. 나머지 개체는 크게 변경되지 않았습니다. 따라서 이러한double[]
개체가 이 2세대 가비지 수집이 발생한 이유였습니다. 다음 단계는 왜double[]
개체가 존재하고 왜 죽었는지를 파악하는 것입니다. 코드 개발자에게 이러한 개체의 원본을 물어보거나 명령을 사용할gcroot
수 있습니다.
높은 CPU 사용량이 가비지 수집으로 인한 것인지 확인하려면
% Time in GC
메모리 성능 카운터 값과 프로세스 시간의 상관 관계를 지정합니다.% Time in GC
프로세스 시간과 동시에 값이 급증하면 가비지 수집으로 인해 CPU 사용량이 높아집니다. 그렇지 않으면 애플리케이션을 프로파일링하여 사용량이 많은 위치를 찾습니다.
참고하십시오
.NET