企业网站板块,网站建设需求方案文档,wordpress 登陆后跳转,免费软件英文一#xff1a;背景 1. 讲故事前几天有位朋友加wx求助说他的程序最近总是出现内存溢出#xff0c;很崩溃#xff0c;如下图#xff1a;和这位朋友聊下来#xff0c;发现他也是搞医疗的#xff0c;哈哈#xff0c;.NET 在医疗方面还是很有市场的????????????背景 1. 讲故事前几天有位朋友加wx求助说他的程序最近总是出现内存溢出很崩溃如下图和这位朋友聊下来发现他也是搞医疗的哈哈.NET 在医疗方面还是很有市场的????????????不过对于内存方面出的问题我得先祈祷一下千万不要是非托管。。。废话不多说上 windbg看能不能先救个急。二windbg 分析 1. 找出异常对象如果内存溢出了大家应该知道 C# 会抛一个 OutOfMemoryException 异常而且还会附加到那个执行线程上所以先用 !t 命令调出当前的所有托管线程。
0:000 !t
ThreadCount: 17
UnstartedThread: 0
BackgroundThread: 12
PendingThread: 0
DeadThread: 4
Hosted Runtime: noLock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception0 1 16b0 007da908 26020 Preemptive 64EDD188:00000000 00823830 1 STA System.OutOfMemoryException 57b53d902 2 af8 007e9dc8 2b220 Preemptive 00000000:00000000 007d4838 0 MTA (Finalizer) 3 3 1d94 0081af28 21220 Preemptive 00000000:00000000 007d4838 0 Ukn 5 6 246c 0772b960 102a220 Preemptive 00000000:00000000 007d4838 0 MTA (Threadpool Worker) 8 47 277c 2eebf038 8029220 Preemptive 00000000:00000000 007d4838 0 MTA (Threadpool Completion Port)
XXXX 41 0 2eebf580 1039820 Preemptive 00000000:00000000 007d4838 0 Ukn (Threadpool Worker)可以清楚的看到0号 线程果然带了一个 System.OutOfMemoryException接下来用 !pe 查查这个异常的调用栈信息。
0:000 !pe 57b53d90
Exception object: 57b53d90
Exception type: System.OutOfMemoryException
Message: 没有足够的内存继续执行程序。
InnerException: none
StackTrace (generated):SP IP Function00482C80 6450BD46 mscorlib_ni!System.Runtime.InteropServices.Marshal.AllocHGlobal(IntPtr)0xc2fdf600482CB0 198DCEF2 UNKNOWN!FastReport.Export.TTF.TrueTypeCollection..ctor(System.Drawing.Font)0xe200482D00 198DCC0F UNKNOWN!FastReport.Export.TTF.ExportTTFFont.GetFontData()0x4700482D58 198DAD54 UNKNOWN!FastReport.Export.Pdf.PDFExport.WriteFont(FastReport.Export.TTF.ExportTTFFont)0xa400483A7C 198D9CD5 UNKNOWN!FastReport.Export.Pdf.PDFExport.AddPDFFooter()0x8d00483C38 198D9B53 UNKNOWN!FastReport.Export.Pdf.PDFExport.Finish()0x2300483C80 19938119 UNKNOWN!FastReport.Export.ExportBase.Export(FastReport.Report, System.IO.Stream)0x22900483CD8 19937A9D UNKNOWN!FastReport.Export.ExportBase.Export(FastReport.Report, System.String)0x4d00483D08 19937A3D UNKNOWN!FastReport.Report.Export(FastReport.Export.ExportBase, System.String)0xd00483D10 15D9FA39 UNKNOWN!xxxx.xxx.FormPrint.PrintPdf(Boolean, System.String, xxxx.DAL.xxx.DataObject.IPatinfoBase, Boolean, System.String)0x35900483DF0 137B265A UNKNOWN!xxxx.UI.xxx.PrintOrdert2PDF.Handle(System.Object[])0x3ca00483EB4 1178B36C xxx_PrintOrder2Pdf!xxxx.xxx.PrintOrder2Pdf.Form1.timer1_Tick(System.Object, System.EventArgs)0xca40048414C 117884DD UNKNOWN!System.Windows.Forms.Timer.OnTick(System.EventArgs)0x1500484154 117883A0 UNKNOWN!System.Windows.Forms.TimerTimerNativeWindow.WndProc(System.Windows.Forms.Message ByRef)0x3800484160 07C939B7 UNKNOWN!System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)0x5f从上面的调用栈可以看出貌似程序是在做一个 pdf 打印最后在 Marshal.AllocHGlobal 上抛了异常熟悉这个方法的朋友应该知道它就是用来分配 非托管内存 的。。。情况貌似有点不妙。????????????接下来用 ILSpy 查一下 AllocHGlobal 方法的源码看看有什么可挖掘的地方。从图中源码逻辑可以看出一旦非托管内存分配失败托管层上手工抛出 OutOfMemoryException 异常我去这难道是非托管内存溢出啦2. 真的是非托管溢出了吗要鉴别是否为非托管堆出的问题还是用那个老办法看看 MEM_COMMIT Size ≈ GC Heap Size 即可。用 !address -summary 查看进程的内存使用量
0:000 !address -summary--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
unknown 16334 460bb000 ( 1.094 GB) 78.00% 54.72%
Free 11177 26319000 ( 611.098 MB) 29.84%
Image 831 e48e000 ( 228.555 MB) 15.91% 11.16%
Heap 184 4547000 ( 69.277 MB) 4.82% 3.38%
Stack 61 11c0000 ( 17.750 MB) 1.24% 0.87%
Other 10 60000 ( 384.000 kB) 0.03% 0.02%
TEB 20 24000 ( 144.000 kB) 0.01% 0.01%
PEB 1 3000 ( 12.000 kB) 0.00% 0.00%--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_COMMIT 16213 521bd000 ( 1.283 GB) 91.43% 64.15%
MEM_FREE 11177 26319000 ( 611.098 MB) 29.84%
MEM_RESERVE 1228 7b1a000 ( 123.102 MB) 8.57% 6.01%从上面的 MEM_COMMIT 指标可以看出内存使用量为 1.28 G。用 !gcheap -gc 看看托管堆的大小
0:000 !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x64c534f8
generation 1 starts at 0x64bccb84
generation 2 starts at 0x02531000
ephemeral segment allocation context: noneGC Heap Size: Size: 0x195be7b0 (425453488) bytes.从最后一行可以看出托管堆占用了 425453488/1024/1024 405M。也就是说大概 800M 不知道哪里去了看似有点吓人其实算算也还可以这里我稍微补充一下看下面的公式MEM_COMMIT (1.28G) Image (228M) Heap (69M) Stack (18M) GCHeap(450M) GCLoader (153M) else 918M从上面列出来的信息可以看出最后累积出的 918M 和 内存使用量 1.28G 差不了多少有些朋友可能要问, 这个 GCLoader 怎么算出来的很简单它是 CLR 的加载堆使用 !eeheap -loader 即可。
0:000 !eeheap -loader
--------------------------------------
Total LoaderHeap size: Size: 0x995a000 (160800768) bytes total, 0x13e000 (1302528) bytes wasted.
到这里我陷入了僵局????????????才 1.28G 的内存占用怎么就会把程序给弄溢出了既然内存上看不出问题那就从线程上入手吧看看他们都在做什么3. 查看每个线程都在做什么要想看线程可以用 ~*e !clrstack 调出所有线程的托管栈突然我发现主线程有点奇怪调用栈特别深不信我截图跟你看。从图中可以看到xxx.xxx.PrintOrder2Pdf.Form1.timer1_Tick 高达 133 个这说明 Form 窗体上有一个 timer 没有控制好出现重复执行的情况了不管怎么说这个地方肯定有问题接下来要做的就是把这个 timer1_Tick 源码导出来看看怎么写的还是用那个 !name2ee !savemodule 老命令导出代码简化如下。
private void timer1_Tick(object sender, EventArgs e)
{if (!IsContinue){PrintMsg(等待上一扫描执行完毕);IsContinue true;return;}IsContinue false;GetPatList();if (PatList null || PatList.Rows.Count 0){timer1.Interval 600000;PrintMsg(xxxx);IsContinue true;return;}for (int i 0; i PatList.Rows.Count; i){xxx}IsContinuetrue;
}从代码中可以看出这个方法用了很多的 IsContinue 来踢掉重复请求但最终还是出了bug导致无限量递归跟朋友沟通后建议用 Stop() 和 Start() 来处理参考如下代码private void button1_Click(object sender, EventArgs e){timer1.Interval 2000;timer1.Tick Timer1_Tick;timer1.Start();}private void Timer1_Tick(object sender, EventArgs e){timer1.Stop();MessageBox.Show(hello);timer1.Start();}起码这种 停止 再 启动 的方式肯定能规避timer的重复执行先把这个改了再说给医院那边先部署上再观后效。。。三总结 朋友在五一节后也就是前天给医院部署上了昨天反馈没有再出现问题截一张图证明一下????????????。大家应该也看的出来其实我心里是没底的。。。后续和朋友再沟通发现了三点信息医生的电脑配置为 8G or 12G有时候为了一些便利医生会开双进程还有更多其他模块的内存溢出案例看了下程序是采用插件式编程而且还用了 DevExpress FastReport 这些重量级的组件再搭配上医生开的双进程让电脑余下的贫瘠内存更加吃紧可能这才是程序在 1.2G 就分配不到非托管内存的深层原因现场情况应该更复杂只能先到这里了。建议措施如下很简单。增加电脑的配置up 到 16G 最好了毕竟甲方都不差钱 ????????????