이 문서의 적용 대상: ✔️ .NET Core 3.1 SDK 이상 버전
앱이 더 이상 원하는 작업을 수행할 필요가 없는 개체를 참조할 때 메모리가 누수될 수 있습니다. 이러한 개체를 참조하면 가비지 수집기가 사용된 메모리를 회수할 수 없습니다. 이로 인해 성능이 저하되고 예외가 OutOfMemoryException throw될 수 있습니다.
이 자습서에서는 .NET 진단 CLI 도구를 사용하여 .NET 앱에서 메모리 누수를 분석하는 도구를 보여 줍니다. Windows를 사용하는 경우 Visual Studio의 메모리 진단 도구를 사용하여 메모리 누수를 디버그할 수 있습니다.
이 자습서에서는 의도적으로 메모리를 누수하는 샘플 앱을 연습으로 사용합니다. 의도치 않게 메모리를 누수하는 앱을 분석할 수도 있습니다.
이 자습서에서는 다음을 수행합니다.
- dotnet-counters를 사용하여 관리되는 메모리 사용량을 검사합니다.
- 덤프 파일을 생성합니다.
- 덤프 파일을 사용하여 메모리 사용량을 분석합니다.
필수 조건
이 자습서에서는 다음을 사용합니다.
- .NET Core 3.1 SDK 이상 버전.
- dotnet-counters 를 사용하여 관리되는 메모리 사용량을 확인합니다.
- 덤프 파일을 수집하고 분석하는 dotnet-dump(SOS 디버깅 확장명 포함).
- 진단할 샘플 디버그 대상 앱입니다.
이 자습서에서는 샘플 앱과 도구가 설치되어 있고 사용할 준비가 되었다고 가정합니다.
관리되는 메모리 사용량 검사
이 시나리오의 근본 원인을 찾기 위해 진단 데이터 수집을 시작하기 전에 메모리 누수(메모리 사용량 증가)가 실제로 표시되는지 확인합니다. dotnet-counters 도구를 사용하여 확인할 수 있습니다.
콘솔 창을 열고 샘플 디버그 대상을 다운로드하고 압축을 푼 디렉터리로 이동합니다. 대상을 실행합니다.
dotnet run
별도의 콘솔에서 프로세스 ID를 찾습니다.
dotnet-counters ps
출력은 다음과 유사해야 합니다.
4807 DiagnosticScena /home/user/git/samples/core/diagnostics/DiagnosticScenarios/bin/Debug/netcoreapp3.0/DiagnosticScenarios
비고
이전 명령이 작동하지 않거나 찾을 수 없는 경우 먼저 도구를 설치 dotnet-counters
해야 할 수 있습니다. 다음 명령을 사용합니다.
dotnet tool install --global dotnet-counters
이제 dotnet-counters 도구를 사용하여 관리되는 메모리 사용량을 확인합니다.
--refresh-interval
새로고침 사이의 시간(초)을 지정합니다.
dotnet-counters monitor --refresh-interval 1 -p 4807
라이브 출력은 다음과 유사해야 합니다.
Press p to pause, r to resume, q to quit.
Status: Running
[System.Runtime]
# of Assemblies Loaded 118
% Time in GC (since last GC) 0
Allocation Rate (Bytes / sec) 37,896
CPU Usage (%) 0
Exceptions / sec 0
GC Heap Size (MB) 4
Gen 0 GC / sec 0
Gen 0 Size (B) 0
Gen 1 GC / sec 0
Gen 1 Size (B) 0
Gen 2 GC / sec 0
Gen 2 Size (B) 0
LOH Size (B) 0
Monitor Lock Contention Count / sec 0
Number of Active Timers 1
ThreadPool Completed Work Items / sec 10
ThreadPool Queue Length 0
ThreadPool Threads Count 1
Working Set (MB) 83
이 줄에 집중:
GC Heap Size (MB) 4
시작 직후 관리되는 힙 메모리가 4MB임을 확인할 수 있습니다.
이제 URL https://localhost:5001/api/diagscenario/memleak/20000
로 이동합니다.
메모리 사용량이 30MB로 증가했는지 확인합니다.
GC Heap Size (MB) 30
메모리 사용량을 확인하여 메모리가 증가하거나 누수되고 있다고 안전하게 말할 수 있습니다. 다음 단계는 메모리 분석에 적합한 데이터를 수집하는 것입니다.
메모리 덤프 생성
가능한 메모리 누수 분석 시 메모리 콘텐츠를 분석하려면 앱의 메모리 힙에 액세스해야 합니다. 개체 간의 관계를 살펴보면 메모리가 해제되지 않는 이유에 대한 이론을 만듭니다. 일반적인 진단 데이터 원본은 Windows의 메모리 덤프 또는 Linux의 해당 코어 덤프입니다. .NET 애플리케이션의 덤프를 생성하려면 dotnet-dump 도구를 사용할 수 있습니다.
이전에 시작된 샘플 디버그 대상 을 사용하여 다음 명령을 실행하여 Linux 코어 덤프를 생성합니다.
dotnet-dump collect -p 4807
결과는 동일한 폴더에 있는 코어 덤프입니다.
Writing minidump with heap to ./core_20190430_185145
Complete
비고
시간에 따른 비교를 위해 첫 번째 덤프를 수집한 후 원래 프로세스가 계속 실행되도록 하고 동일한 방식으로 두 번째 덤프를 수집합니다. 그런 다음 일정 기간 동안 두 개의 덤프를 사용하여 메모리 사용량이 증가하는 위치를 비교할 수 있습니다.
실패한 프로세스를 다시 시작합니다.
덤프가 수집되면 실패한 프로세스를 진단하기에 충분한 정보가 있어야 합니다. 실패한 프로세스가 프로덕션 서버에서 실행 중인 경우 이제 프로세스를 다시 시작하여 단기 수정하기에 이상적인 시기입니다.
이 자습서에서는 이제 샘플 디버그 대상 을 완료하고 닫을 수 있습니다. 서버를 시작한 터미널로 이동하고 Ctrl+C를 누릅니다.
코어 덤프 분석
이제 코어 덤프가 생성되었으므로 dotnet-dump 도구를 사용하여 덤프를 분석합니다.
dotnet-dump analyze core_20190430_185145
분석하려는 코어 덤프의 이름을 core_20190430_185145
로 지정합니다.
비고
libdl.so 찾을 수 없다는 오류가 표시되면 libc6-dev 패키지를 설치해야 할 수 있습니다. 자세한 내용은 Linux의 .NET에 대한 필수 구성 요소를 참조하세요.
SOS 명령을 입력할 수 있는 프롬프트가 표시됩니다. 일반적으로 가장 먼저 확인하려는 것은 관리되는 힙의 전체 상태입니다.
> dumpheap -stat
Statistics:
MT Count TotalSize Class Name
...
00007f6c1eeefba8 576 59904 System.Reflection.RuntimeMethodInfo
00007f6c1dc021c8 1749 95696 System.SByte[]
00000000008c9db0 3847 116080 Free
00007f6c1e784a18 175 128640 System.Char[]
00007f6c1dbf5510 217 133504 System.Object[]
00007f6c1dc014c0 467 416464 System.Byte[]
00007f6c21625038 6 4063376 testwebapi.Controllers.Customer[]
00007f6c20a67498 200000 4800000 testwebapi.Controllers.Customer
00007f6c1dc00f90 206770 19494060 System.String
Total 428516 objects
여기에서 대부분의 개체가 String
또는 Customer
개체임을 확인할 수 있습니다.
MT(메서드 테이블)와 함께 명령을 다시 사용하여 dumpheap
모든 String
인스턴스 목록을 가져올 수 있습니다.
> dumpheap -mt 00007f6c1dc00f90
Address MT Size
...
00007f6ad09421f8 00007faddaa50f90 94
...
00007f6ad0965b20 00007f6c1dc00f90 80
00007f6ad0965c10 00007f6c1dc00f90 80
00007f6ad0965d00 00007f6c1dc00f90 80
00007f6ad0965df0 00007f6c1dc00f90 80
00007f6ad0965ee0 00007f6c1dc00f90 80
Statistics:
MT Count TotalSize Class Name
00007f6c1dc00f90 206770 19494060 System.String
Total 206770 objects
이제 인스턴스의 gcroot
명령을 System.String
사용하여 개체가 루팅되는 방법과 이유를 확인할 수 있습니다.
> gcroot 00007f6ad09421f8
Thread 3f68:
00007F6795BB58A0 00007F6C1D7D0745 System.Diagnostics.Tracing.CounterGroup.PollForValues() [/_/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/CounterGroup.cs @ 260]
rbx: (interior)
-> 00007F6BDFFFF038 System.Object[]
-> 00007F69D0033570 testwebapi.Controllers.Processor
-> 00007F69D0033588 testwebapi.Controllers.CustomerCache
-> 00007F69D00335A0 System.Collections.Generic.List`1[[testwebapi.Controllers.Customer, DiagnosticScenarios]]
-> 00007F6C000148A0 testwebapi.Controllers.Customer[]
-> 00007F6AD0942258 testwebapi.Controllers.Customer
-> 00007F6AD09421F8 System.String
HandleTable:
00007F6C98BB15F8 (pinned handle)
-> 00007F6BDFFFF038 System.Object[]
-> 00007F69D0033570 testwebapi.Controllers.Processor
-> 00007F69D0033588 testwebapi.Controllers.CustomerCache
-> 00007F69D00335A0 System.Collections.Generic.List`1[[testwebapi.Controllers.Customer, DiagnosticScenarios]]
-> 00007F6C000148A0 testwebapi.Controllers.Customer[]
-> 00007F6AD0942258 testwebapi.Controllers.Customer
-> 00007F6AD09421F8 System.String
Found 2 roots.
String
는 Customer
개체에 의해 직접 보유되며, CustomerCache
개체에 의해 간접적으로 보유되는 것을 볼 수 있습니다.
개체를 계속 덤프하여 대부분의 String
개체가 비슷한 패턴을 따르는지 확인할 수 있습니다. 이 시점에서 조사는 코드의 근본 원인을 식별하기에 충분한 정보를 제공했습니다.
이 일반적인 절차를 통해 주요 메모리 누수의 원인을 식별할 수 있습니다.
자원을 정리하세요
이 자습서에서는 샘플 웹 서버를 시작했습니다. 실패한 프로세스 다시 시작 섹션에 설명된 대로 이 서버가 종료되어야 합니다.
만든 덤프 파일을 삭제할 수도 있습니다.
참고하십시오
- 프로세스를 나열하는 dotnet-trace
- 관리되는 메모리 사용량을 확인하기 위한 dotnet-counters
- 덤프 파일을 수집하고 분석하는 dotnet-dump
- dotnet/diagnostics
- Visual Studio를 사용하여 메모리 누수 디버그
다음 단계
.NET