성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
[공진영] 안녕하세요 좋은글 감사합니다. 현재 제가 wpf로 관제 모...
[정성태] The Windows Registry Adventure #1: ...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>windbg - 닷넷 응용 프로그램의 메모리 누수 분석</h1> <p> 덤프를 받았는데, 문제 현상은 메모리 누수였습니다. 메모리 누수를 분석하기 위한 전형적인 방식으로, 우선 확실한 분석을 위해 일정 시간 간격을 두고 메모리 덤프를 받아야 합니다. (문제가 단순한 경우 하나만 받아도 충분합니다.) <br /> <br /> 그다음 GC heap 상태를 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!dumpheap -stat</span> Statistics: MT Count TotalSize Class Name 00007ff90f456748 1 24 Elasticsearch.Net.ElasticsearchDefaultSerializer 00007ff90f192f78 1 24 System.Collections.Generic.ObjectEqualityComparer`1[[System.Runtime.Caching.MemoryCache, System.Runtime.Caching]] 00007ff90f143038 1 24 System.Collections.Generic.Dictionary`2+ValueCollection[[System.String, mscorlib],[OracleInternal.SelfTuning.OracleTuner+OracleTunerInput, Oracle.ManagedDataAccess]] 00007ff90f0120b8 1 24 System.Net.SSPISecureChannelType 00007ff90f011e78 1 24 System.Net.SSPIAuthType ...[생략]... 00007ff90d3869b8 12753 5127112 System.Int32[] 00007ff90d4ba6e0 159 14555944 System.Int64[] 00007ff90d1aba28 148673 16952928 System.Object[] 00007ff90d1fe828 75889 20114243 System.Byte[] <span style='color: blue; font-weight: bold'>00007ff90ebd41e0 376168 36112128 TestApi.Data.AddrInfo 00007ff90d1f46f0 2726523 208786640 System.String</span> 000000b69ba94870 33952 274313352 Free Total 4063087 objects Fragmented blocks larger than 0.5 MB: Addr Size Followed by 000000b6a40f38b0 11.4MB 000000b6a4c4d630 System.Byte[] </pre> <br /> 위의 경우 문자열(System.String)이 유독 많이 생성된 것을 볼 수 있습니다. 2개의 메모리 덤프에 대해 GC heap을 비교하면 실제로 System.String이 메모리 누수의 원인임을 더 확실하게 인지할 수 있습니다.<br /> <br /> System.String이 누수인 것은, 당연히 그것을 참조하는 객체가 살아 있기 때문입니다. 다행히 위의 결과에서는 TestApi.Data.AddrInfo 사용자 타입이 유독 많은 걸로 봐서 쉽게 짐작할 수 있는데요, 저 타입의 정의를 찾아서 클래스 구조를 확인하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!name2ee *!TestApi.Data.AddrInfo</span> ...[생략]... ------------------------------ Module: 00007ff90dc99148 Assembly: TestApi.Repo.dll Token: 0000000002000002 MethodTable: 00007ff90ebd41e0 <span style='color: blue; font-weight: bold'>EEClass: 00007ff90ec714d0</span> Name: TestApi.Data.AddrInfo -------------------------------------- ...[생략]... 0:000> <span style='color: blue; font-weight: bold'>!DumpClass /d 00007ff90ec714d0</span> Class Name: TestApi.Data.AddrInfo mdToken: 0000000002000002 File: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root\21a9bed8\2f9be145\assembly\dl3\c9594379\8650e087_aa7fd401\TestApi.Repo.dll Parent Class: 00007ff90d1d3760 Module: 00007ff90dc99148 Method Table: 00007ff90ebd41e0 Vtable Slots: 4 Total Method Slots: 5 Class Attributes: 100001 Transparency: Critical NumInstanceFields: a NumStaticFields: 0 MT Field Offset Type VT Attr Value Name 00007ff90d1f46f0 4000001 8 System.String 0 instance <Code>k__BackingField 00007ff90d1f46f0 4000002 10 System.String 0 instance <RoadAddr>k__BackingField 00007ff90d1f46f0 4000003 18 System.String 0 instance <RoadAddr2>k__BackingField 00007ff90d1f46f0 4000004 20 System.String 0 instance <RoadAddr3>k__BackingField 00007ff90d1f46f0 4000005 28 System.String 0 instance <CodeRoadAddr>k__BackingField 00007ff90d1f46f0 4000006 30 System.String 0 instance <CodeRoadAddr2>k__BackingField 00007ff90d1f46f0 4000007 38 System.String 0 instance <CodeRoadAddr3>k__BackingField 00007ff90d1af6f0 4000008 50 System.Int32 1 instance <CodeHash>k__BackingField 00007ff90d1f46f0 4000009 40 System.String 0 instance <Reserved1>k__BackingField 00007ff90d1f46f0 400000a 48 System.String 0 instance <Reserved2>k__BackingField </pre> <br /> 해당 타입에 9개의 string 멤버가 있습니다. TestApi.Data.AddrInfo 인스턴스 수가 376,168개였으니,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 376,168 * 9 = 3,385,512 (System.String의 인스턴스 수 2,726,523) </pre> <br /> 결과가 맞지 않습니다. 하지만, 혹시나 싶어 TestApi.Data.AddrInfo 인스턴스 몇 개를 덤프해 보니 2개의 필드 값이 null인 것을 볼 수 있습니다. 따라서 이제는 대충 계산이 맞아 들어갑니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 376,168 * 7 = 2,633,176 (System.String의 인스턴스 수 2,726,523) </pre> <br /> 이제 문제는 다시 반복해서, TestApi.Data.AddrInfo 인스턴스들이 어떤 다른 객체에 의해 참조가 안 풀리는지 알아내야 합니다. 이를 위해 376,168개 중의 인스턴스 중에 일부를 조사해 보면 답이 나옵니다. 따라서 GC heap에 있는 TestApi.Data.AddrInfo 인스턴스를 나열하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!DumpHeap -mt 00007ff90ebd41e0</span> ...[생략]... // 376,168개의 출력 결과 <span style='color: blue; font-weight: bold'>000000b99d5e7920</span> 00007ff90ebd41e0 96 000000b9a5c1b098 00007ff90ebd41e0 96 000000b9a5c572b8 00007ff90ebd41e0 96 000000b9a60f1420 00007ff90ebd41e0 96 Statistics: MT Count TotalSize Class Name 00007ff90ebd41e0 376168 36112128 TestApi.Data.AddrInfo Total 376168 objects Fragmented blocks larger than 0.5 MB: Addr Size Followed by 000000b6a40f38b0 11.4MB 000000b6a4c4d630 System.Byte[] ...[생략]... </pre> <br /> 그중에서 대충 몇 개를 골라 참조 관계를 뽑아 봅니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!gcroot 000000b99d5e7920</span> Thread 1a70: *** WARNING: Unable to verify checksum for NetworkQueue.Logger.dll *** ERROR: Module load completed but symbols could not be loaded for NetworkQueue.Logger.dll 000000baf279ebe0 00007ff90d96c087 NetworkQueue.NetManager.Loop() rbp+c0: 000000baf279ecc0 -> 000000b79bd5ad48 NetworkQueue.NetManager -> 000000b79bd5afb8 System.Collections.Generic.List`1[[NetworkQueue.NetManager+EntitySet, NetworkQueue.Logger]] -> 000000b99bcc2ec8 System.Object[] -> 000000b79bd5b650 NetworkQueue.NetManager+EntitySet -> 000000b79bd58bb8 System.Collections.Generic.List`1[[NetworkQueue.SocketInstance, NetworkQueue.Logger]] -> 000000b79bd5e800 System.Object[] -> 000000b79bd5d490 NetworkQueue.Insert -> 000000b79bd5d560 System.Collections.Generic.HashSet`1[[NetworkQueue.Container, NetworkQueue.Logger]] -> 000000b79be41b00 System.Collections.Generic.HashSet`1+Slot[[NetworkQueue.Container, NetworkQueue.Logger]][] -> 000000b79bd5f118 NetworkQueue.Insert+ItemContext -> 000000b79bd5c368 System.Threading.Thread -> 000000b79bce3168 System.Runtime.Remoting.Contexts.Context -> 000000b79bce2e68 System.AppDomain -> 000000b69bd44600 System.AssemblyLoadEventHandler -> 000000b69bd44528 System.Web.Compilation.MemoryBuildResultCache -> 000000b69bccd530 System.Web.Caching.CacheMultiple -> 000000b69bccd1f0 System.Web.Caching.CacheCommon -> 000000b69bcd2998 System.Threading.Timer -> 000000b69bcd2a10 System.Threading.TimerHolder -> 000000b69bcd29b8 System.Threading.TimerQueueTimer -> 000000b89be46228 System.Threading.TimerQueueTimer -> 000000b89be461c8 System.Threading.TimerCallback -> 000000b89be45478 System.Runtime.Caching.MemoryCacheStatistics -> 000000b89be46088 System.Runtime.Caching.CacheMemoryMonitor -> 000000b89be3e968 System.Runtime.Caching.MemoryCache -> 000000b89be3e9b8 System.Object[] -> 000000b89be42b78 System.Runtime.Caching.MemoryCacheStore -> 000000b89be42be0 System.Collections.Hashtable -> 000000baabdc1e70 System.Collections.Hashtable+bucket[] -> 000000b99d5ea280 System.Runtime.Caching.MemoryCacheEntry -> 000000b99d5ea1f0 CacheManager.Entity -> 000000b99d5e7688 System.Collections.Generic.List`1[[TestApi.Data.AddrInfo, TestApi.Repo]] -> 000000b99d5e9178 System.Object[] -> 000000b99d5e7920 TestApi.Data.AddrInfo </pre> <br /> 굉장히 복잡하군요. 그래도 대충 보면, Cache한 것들이 지속적으로 늘어나고 있는 중임을 짐작게 합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이번 분석을 하면서 써 본 도구가 있는데 우선 HeapStringAnalyser를 소개합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > mattwarren/HeapStringAnalyser ; <a target='tab' href='https://github.com/mattwarren/HeapStringAnalyser'>https://github.com/mattwarren/HeapStringAnalyser</a> </pre> <br /> 빌드 배포본이 없으므로 git clone을 통해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > c:\git_clone> <span style='color: blue; font-weight: bold'>git clone https://github.com/mattwarren/HeapStringAnalyser.git</span> Cloning into 'HeapStringAnalyser'... remote: Enumerating objects: 72, done. remote: Total 72 (delta 0), reused 0 (delta 0), pack-reused 72 Unpacking objects: 100% (72/72), done. </pre> <br /> 소스 코드로부터 (Visual Studio 등을 이용해) 직접 빌드해야 합니다. 빌드 후, 덤프 파일을 인자로 지정해 실행하는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > c:\git_clone\HeapStringAnalyser\HeapStringAnalyser\HeapStringAnalyser\bin\x64\Debug>HeapStringAnalyser.exe c:\temp\w3wp.dmp .NET Memory Dump Heap Analyser - created by Matt Warren - github.com/mattwarren Found CLR Version: v4.0.30319.34014 <span style='color: blue; font-weight: bold'>Unable to find copy of the dac (mscordacwks_Amd64_Amd64_4.0.30319.34014.dll) on the local machine.</span> Expected location: %TEMP%\symbols\mscordacwks_Amd64_Amd64_4.0.30319.34014.dll\52e0b86c97c000\mscordacwks_Amd64_Amd64_4.0.30319.34014.dll It will now be dContainerloaded from the Microsoft Symbol Server. Press <ENTER> if you are okay with this, if not you can just type Ctrl-C to exit </pre> <br /> 위와 같이 dac 구성 요소를 찾을 수 없다고 나올 수 있습니다. 이건 예전에도 설명했습니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > windbg의 mscordacwks DLL 로드 문제 - 세 번째 이야기 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11231'>http://www.sysnet.pe.kr/2/0/11231</a> </pre> <br /> 따라서 덤프 파일을 생성한 바로 그 컴퓨터의 mscordacwks.dll을 복사해 정확히 "%TEMP%\symbols\mscordacwks_Amd64_Amd64_4.0.30319.34014.dll\52e0b86c97c000\mscordacwks_Amd64_Amd64_4.0.30319.34014.dll" 경로에 해당 DLL 이름으로 복사하면 됩니다. 이제 다시 실행하면 다음과 같은 식의 출력을 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > GC Heap Information - Server ----------------------------------------------------------- Heap 0: 40,286,552 bytes (38.42 MB) in use ----------------------------------------------------------- Type Size (MB) Committed (MB) Reserved (MB) ----------------------------------------------------------- Ephemeral 36.28 63.63 4,096.00 Large 2.14 2.52 256.00 ----------------------------------------------------------- Heap 1: 29,069,336 bytes (27.72 MB) in use ----------------------------------------------------------- Type Size (MB) Committed (MB) Reserved (MB) ----------------------------------------------------------- Ephemeral 26.40 58.00 4,096.00 Large 1.32 1.32 256.00 ----------------------------------------------------------- Heap 2: 38,809,896 bytes (37.01 MB) in use ----------------------------------------------------------- Type Size (MB) Committed (MB) Reserved (MB) ----------------------------------------------------------- Ephemeral 31.17 77.20 4,096.00 Large 5.84 5.85 256.00 ----------------------------------------------------------- Heap 3: 32,697,496 bytes (31.18 MB) in use ----------------------------------------------------------- Type Size (MB) Committed (MB) Reserved (MB) ----------------------------------------------------------- Ephemeral 30.61 75.33 4,096.00 Large 0.57 0.70 256.00 ----------------------------------------------------------- Total (across all heaps): 140,863,280 bytes (134.34 MB) ----------------------------------------------------------- "System.String" memory usage info Overall 995,171 "System.String" objects take up 76,240,374 bytes (72.71 MB) Of this underlying byte arrays (as Unicode) take up 50,365,928 bytes (48.03 MB) Remaining data (object headers, other fields, etc) are 25,874,446 bytes (24.68 MB), at 26 bytes per object Actual Encoding that the "System.String" could be stored as (with corresponding data size) 35,926,222 bytes ( 706,112 strings) as ASCII 3,338 bytes ( 29 strings) as ISO-8859-1 (Latin-1) 14,436,368 bytes ( 289,030 strings) as Unicode Total: 50,365,928 bytes (expected: 50,365,928) Compression Summary: 17,964,780 bytes Compressed (to ISO-8859-1 (Latin-1)) 14,436,368 bytes Uncompressed (as Unicode) 995,171 bytes EXTRA to enable compression (1-byte field, per "System.String" object) Total Usage: 33,396,319 bytes (31.85 MB), compared to 50,365,928 (48.03 MB) before compression Total Saving: 16,969,609 bytes (16.18 MB) </pre> <br /> 보는 바와 같이, 문자열 인스턴스가 GC heap에 어느 정도의 용량을 차지하고 있는지 정도만 알 수 있습니다. (그렇군요, 원인 분석에 그다지 도움이 되는 툴은 아닙니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 또 다른 도구로 MemoScope가 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > fremag/MemoScope.Net ; <a target='tab' href='https://github.com/fremag/MemoScope.Net'>https://github.com/fremag/MemoScope.Net</a> </pre> <br /> 사실 이걸 더 추천하고 싶습니다. ^^ <br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='memoscope_typestats.png' src='/SysWebRes/bbs/memoscope_typestats.png' /><br /> <br /> 보는 바와 같이, windbg로 "!dumpheap", "!dumpclass", "!do" 명령 등을 그냥 UI로 간편하게 처리할 수 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 마지막으로 메모리 누수의 또 다른 지표인 finalizer 상황도 함께 보면 좋습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > windbg - 닷넷 Finalizer 스레드가 멈춰있는 현상 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11803'>http://www.sysnet.pe.kr/2/0/11803</a> </pre> <br /> 아래는 실행 결과의 예인데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!finalizequeue</span> SyncBlocks to be cleaned up: 0 Free-Threaded Interfaces to be released: 0 MTA Interfaces to be released: 0 STA Interfaces to be released: 0 ---------------------------------- ------------------------------ Heap 0 generation 0 has 169 finalizable objects (000000baf48f7b00->000000baf48f8048) generation 1 has 3 finalizable objects (000000baf48f7ae8->000000baf48f7b00) <span style='color: blue; font-weight: bold'>generation 2 has 347 finalizable objects (000000baf48f7010->000000baf48f7ae8) Ready for finalization 0 objects (000000baf48f8048->000000baf48f8048)</span> ------------------------------ Heap 1 generation 0 has 83 finalizable objects (000000baf4dd8078->000000baf4dd8310) generation 1 has 2 finalizable objects (000000baf4dd8068->000000baf4dd8078) <span style='color: blue; font-weight: bold'>generation 2 has 7685 finalizable objects (000000baf4dc9040->000000baf4dd8068) Ready for finalization 0 objects (000000baf4dd8310->000000baf4dd8310)</span> ------------------------------ Heap 2 generation 0 has 1604 finalizable objects (000000baf1cd7020->000000baf1cda240) generation 1 has 1 finalizable objects (000000baf1cd7018->000000baf1cd7020) <span style='color: blue; font-weight: bold'>generation 2 has 1139 finalizable objects (000000baf1cd4c80->000000baf1cd7018) Ready for finalization 0 objects (000000baf1cda240->000000baf1cda240)</span> ------------------------------ Heap 3 generation 0 has 368 finalizable objects (000000baf4c71020->000000baf4c71ba0) generation 1 has 6 finalizable objects (000000baf4c70ff0->000000baf4c71020) <span style='color: blue; font-weight: bold'>generation 2 has 1018 finalizable objects (000000baf4c6f020->000000baf4c70ff0) Ready for finalization 0 objects (000000baf4c71ba0->000000baf4c71ba0)</span> Statistics for all finalizable objects (including all objects ready for finalization): ...[생략]... 00007ff90e0d9ff8 195 14040 TestFx.Diagnostics.TraceEntry 00007ff90df4aad8 588 42336 System.Reflection.Emit.DynamicResolver 00007ff90e97e7c8 10402 249648 System.Reflection.Emit.DynamicResolver+DestroyScout Total 12425 objects </pre> <br /> "<a target='tab' href='http://www.sysnet.pe.kr/2/0/11803'>windbg - 닷넷 Finalizer 스레드가 멈춰있는 현상</a>" 글에 쓴 것처럼 Finalizer 스레드가 hang 상태라면 위의 출력 결과에서 "Ready for finalization"의 수치가 증가하는 것이 확인됩니다.<br /> <br /> 반면 Finalizer는 문제없지만 종료자를 구현한 객체들이 누수되고 있는 경우 위에서와 같이 "generation 2 has xxxx finalizable objects" 항목을 통해 (2개의 덤프 파일 간에) 그 수가 점점 늘어나고 있는 것을 보게 됩니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1267
(왼쪽의 숫자를 입력해야 합니다.)