.NET Windbg分析某婦產(chǎn)醫(yī)院WPF內(nèi)存溢出
一:背景
1. 講故事
上個(gè)月有位朋友找到我,說(shuō)他的程序存在內(nèi)存溢出情況,尋求如何解決。
要解決還得通過(guò) windbg 分析啦。
二:Windbg 分析
1. 為什么會(huì)內(nèi)存溢出
大家都知道內(nèi)存溢出對(duì)應(yīng)著 .NET 中的 OutOfMemoryException 異常,這種異常有可能是托管代碼手工拋出的,也有可能是CLR層面拋出的,言外之意就是可以通過(guò)兩種方式排查。
- 托管線程是否掛載著異常?
0:000> !t ThreadCount: 23 UnstartedThread: 0 BackgroundThread: 5 PendingThread: 0 DeadThread: 17 Hosted Runtime: no Lock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 0 1 362c 00fac868 26020 Preemptive 7ED701A0:00000000 00fa6b60 0 STA 5 2 2d70 00fbeba0 2b220 Preemptive 7EBA7AC0:00000000 00fa6b60 0 MTA (Finalizer) 7 3 3264 061c8890 102a220 Preemptive 00000000:00000000 00fa6b60 0 MTA (Threadpool Worker) 17 15 3f98 19682b90 202b220 Preemptive 7EBB0830:00000000 00fa6b60 0 MTA XXXX 16 0 2845fb00 35820 Preemptive 00000000:00000000 00fa6b60 0 Ukn 18 14 a7c 2842b1c8 202b220 Preemptive 00000000:00000000 00fa6b60 0 MTA XXXX 6 0 2c9b3778 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker) XXXX 18 0 288a1318 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker) XXXX 23 0 288a22f0 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker) XXXX 10 0 2ccf3550 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker) XXXX 21 0 288a1860 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker) XXXX 12 0 288a1da8 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker) XXXX 11 0 2c993640 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker) XXXX 8 0 2ccf3a98 35820 Preemptive 00000000:00000000 00fa6b60 0 Ukn XXXX 9 0 2ccf2030 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker) XXXX 7 0 2c9aed88 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker) XXXX 26 0 28898308 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker) XXXX 25 0 2c492c68 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker) XXXX 4 0 2c993b88 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker) XXXX 20 0 2c9af2d0 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker) XXXX 17 0 2c9afd60 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker) XXXX 24 0 2c9b1280 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker) 23 22 2658 2c9b02a8 1029220 Preemptive 7ED5BFF8:00000000 00fa6b60 0 MTA (Threadpool Worker)
從輸出信息看,這些線程并沒有掛載任何托管異常,我去。。。
- 是否在 CLR 上拋出
這主要是看 托管堆(heap) 上的內(nèi)存分配或者gc回收造成的內(nèi)存不足,可以用 !ao 命令。
0:000> !ao There was no managed OOM due to allocations on the GC heap
從輸出信息看也沒有任何異常,尷尬了??????。。。 尼瑪,那到底是因?yàn)槭裁茨兀?/p>
2. 探索溢出原因
出現(xiàn)這種尷尬情況,我只能懷疑生成這個(gè)dump的時(shí)候并沒有g(shù)et到那個(gè)點(diǎn),或者是我的知識(shí)邊界有限,不過(guò)天無(wú)絕人之路,不在那個(gè) 點(diǎn) 也肯定在那個(gè) 點(diǎn) 附近,對(duì)吧,接下來(lái)用 !address -summary 看一下內(nèi)存使用的歸類信息。
0:000> !address -summary --- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal <unknown> 1520 4c185000 ( 1.189 GB) 65.57% 59.45% Image 4306 1f140000 ( 497.250 MB) 26.78% 24.28% Free 1133 bf17000 ( 191.090 MB) 9.33% Heap 617 7626000 ( 118.148 MB) 6.36% 5.77% Stack 72 1740000 ( 23.250 MB) 1.25% 1.14% Other 34 7b000 ( 492.000 kB) 0.03% 0.02% TEB 24 30000 ( 192.000 kB) 0.01% 0.01% PEB 1 3000 ( 12.000 kB) 0.00% 0.00% --- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal MEM_MAPPED 549 34b60000 ( 843.375 MB) 45.42% 41.18% MEM_PRIVATE 1718 20424000 ( 516.141 MB) 27.80% 25.20% MEM_IMAGE 4307 1f155000 ( 497.332 MB) 26.78% 24.28% --- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal MEM_COMMIT 4904 66ddd000 ( 1.607 GB) 88.64% 80.37% MEM_RESERVE 1670 d2fc000 ( 210.984 MB) 11.36% 10.30% MEM_FREE 1133 bf17000 ( 191.090 MB) 9.33% --- Protect Summary (for commit) - RgnCount ----------- Total Size -------- %ofBusy %ofTotal PAGE_READONLY 2272 382cf000 ( 898.809 MB) 48.41% 43.89% PAGE_READWRITE 1572 1eead000 ( 494.676 MB) 26.64% 24.15% PAGE_EXECUTE_READ 218 dd59000 ( 221.348 MB) 11.92% 10.81% PAGE_WRITECOPY 449 133e000 ( 19.242 MB) 1.04% 0.94% PAGE_EXECUTE_READWRITE 188 ab4000 ( 10.703 MB) 0.58% 0.52% PAGE_NOACCESS 156 9c000 ( 624.000 kB) 0.03% 0.03% PAGE_READWRITE | PAGE_GUARD 48 78000 ( 480.000 kB) 0.03% 0.02% PAGE_READWRITE | PAGE_WRITECOMBINE 1 2000 ( 8.000 kB) 0.00% 0.00% --- Largest Region by Usage ----------- Base Address -------- Region Size ---------- <unknown> 1d200000 a001000 ( 160.004 MB) Image fed1000 36e4000 ( 54.891 MB) Free 33dfe000 1082000 ( 16.508 MB) Heap 3da84000 a1b000 ( 10.105 MB) Stack 1a10000 fd000 (1012.000 kB) Other 7fa40000 33000 ( 204.000 kB) TEB a4c000 3000 ( 12.000 kB) PEB a3d000 3000 ( 12.000 kB)
從上面的 MEM_COMMIT=1.607 GB 80.37% 信息看,當(dāng)前內(nèi)存占用 1.6G,占比 80.37%,可以看出它受到了一個(gè) 2G內(nèi)存 的限制,而且從 !t 輸出中的內(nèi)存地址看,當(dāng)前是 32bit 程序,所以這是一個(gè)經(jīng)典的: 64系統(tǒng)跑著32位程序被2G內(nèi)存限制 的問(wèn)題。
3. 如何突破 2G 限制
要尋找答案,還得看最權(quán)威的 MSDN:
破局 還得設(shè)置程序的 IMAGE_FILE_LARGE_ADDRESS_AWARE 標(biāo)記。
關(guān)于具體怎么設(shè)置,我找了三種方法。
- 使用 LargeAddressAware 安裝包
參見 github:
https://github.com/KirillOsenkov/LargeAddressAware
- 使用 editbin
可以在 vs 的生成事件中輸入 editbin /largeaddressaware $(TargetPath)。
- 使用代碼方式
這種可以直接給生成好的 exe 增加 LargeAddressAware 標(biāo)記,除了標(biāo)記,還能檢測(cè),????
using System; using System.IO; namespace PEFile { public class LargeAddressAware { public static bool IsLargeAddressAware(string filePath) { bool isLargeAddressAware = false; PrepareStream(filePath, (stream, binaryReader) => isLargeAddressAware = (binaryReader.ReadInt16() & 0x20) != 0); return isLargeAddressAware; } public static void SetLargeAddressAware(string filePath) { PrepareStream(filePath, (stream, binaryReader) => { var value = binaryReader.ReadInt16(); if ((value & 0x20) == 0) { value = (short)(value | 0x20); stream.Position -= 2; var binaryWriter = new BinaryWriter(stream); binaryWriter.Write(value); binaryWriter.Flush(); } }); } private static void PrepareStream(string filePath, Action<Stream, BinaryReader> action) { using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.Read)) { if (stream.Length < 0x3C) { return; } var binaryReader = new BinaryReader(stream); // MZ header if (binaryReader.ReadInt16() != 0x5A4D) { return; } stream.Position = 0x3C; var peHeaderLocation = binaryReader.ReadInt32(); stream.Position = peHeaderLocation; // PE header if (binaryReader.ReadInt32() != 0x4550) { return; } stream.Position += 0x12; action(stream, binaryReader); } } } }
更多辦法參考:
三:總結(jié)
總的來(lái)說(shuō),2G 內(nèi)存限制 是一個(gè) 32bit 程序所必須面對(duì)的問(wèn)題,知道了就好解決了,最后有一個(gè)問(wèn)題要解釋下,為什么 commit 內(nèi)存高達(dá) 1.6G,這是因?yàn)獒t(yī)療類的軟件,大多是 FastReport + DevExpress 這些重量級(jí)的經(jīng)典搭配以及大量的圖片資源占用了太多 native memory。
以上就是.NET Windbg分析某婦產(chǎn)醫(yī)院WPF內(nèi)存溢出的詳細(xì)內(nèi)容,更多關(guān)于.NET Windbg WPF內(nèi)存溢出的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
.NET的Ajax請(qǐng)求數(shù)據(jù)提交實(shí)例
這篇文章主要介紹了.NET的Ajax請(qǐng)求數(shù)據(jù)提交實(shí)例,較為詳細(xì)的分析了Ajax請(qǐng)求、數(shù)據(jù)的提交以及參數(shù)的傳遞技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-01-01asp.net 臨時(shí)數(shù)據(jù)保存實(shí)現(xiàn)代碼
在一個(gè)程序運(yùn)行的時(shí)候往往我們會(huì)回到上一個(gè)頁(yè)面,或者我們會(huì)需要當(dāng)時(shí)留在這個(gè)頁(yè)面的臨時(shí)數(shù)據(jù),例如,我們用百度搜索“腳本”之后我們會(huì)看到很多那啥我就不再說(shuō)了啊!然后我們返回本來(lái)頁(yè)面在文本框內(nèi)會(huì)有“腳本”的字樣,我們一下代碼段就是實(shí)現(xiàn)這個(gè)功能2012-04-04詳解在.net core中完美解決多租戶分庫(kù)分表的問(wèn)題
這篇文章主要介紹了詳解在.net core中完美解決多租戶分庫(kù)分表的問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04asp.net上傳Excel文件并讀取數(shù)據(jù)的實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于asp.net上傳Excel文件并讀取數(shù)據(jù)的實(shí)現(xiàn)方法,文中通過(guò)示例代碼以及圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03Ajax實(shí)現(xiàn)異步刷新驗(yàn)證用戶名是否已存在的具體方法
由于要做一個(gè)注冊(cè)頁(yè)面,看到許多網(wǎng)站上都是使用Ajax異步刷新驗(yàn)證用戶名是否可用的,所以自己也動(dòng)手做一個(gè)小實(shí)例2014-02-02ASP.NET比較常用的26個(gè)性能優(yōu)化技巧
這篇文章主要給大家介紹asp.net中比較常用的26個(gè)性能優(yōu)化技巧,主要設(shè)計(jì)到asp.net中常用的26個(gè)性能優(yōu)化方面的內(nèi)容,對(duì)于asp.net中常用的26個(gè)性能優(yōu)化技巧感興趣的朋友可以參考下本篇文章2015-10-10.Net?Core和RabbitMQ限制循環(huán)消費(fèi)的方法
當(dāng)消費(fèi)者端接收消息處理業(yè)務(wù)時(shí),如果出現(xiàn)異?;蚴蔷苁障⑾⒂肿兏鼮榈却哆f再次推送給消費(fèi)者,這樣一來(lái),則形成循環(huán)的條件,這篇文章主要介紹了.Net?Core和RabbitMQ限制循環(huán)消費(fèi)的方法,需要的朋友可以參考下2022-10-10asp.net DataGrid 中文字符排序的實(shí)現(xiàn)代碼
在論壇上看到有位朋友希望對(duì)中文按拼音進(jìn)行排序,剛好最近有點(diǎn)空,貼一份原來(lái)一個(gè)同事寫的一個(gè)排序類,僅稍微改動(dòng)了下下,拿出來(lái)分享下.2009-09-09