<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          記一次 .NET 醫(yī)院CIS系統(tǒng) 內存溢出分析

          共 13826字,需瀏覽 28分鐘

           ·

          2021-05-16 10:58

          一:背景

          1. 講故事

          前幾天有位朋友加wx求助說他的程序最近總是出現(xiàn)內存溢出,很崩潰,如下圖:

          和這位朋友聊下來,發(fā)現(xiàn)他也是搞醫(yī)療的,哈哈,.NET 在醫(yī)療方面還是很有市場的??????,不過對于內存方面出的問題,我得先祈禱一下千萬不要是非托管。。。

          廢話不多說,上 windbg,看能不能先救個急。

          二:windbg 分析

          1. 找出異常對象

          如果內存溢出了,大家應該知道 C# 會拋一個 OutOfMemoryException 異常,而且還會附加到那個執(zhí)行線程上,所以先用 !t 命令調出當前的所有托管線程。


          0:000> !t
          ThreadCount:      17
          UnstartedThread:  0
          BackgroundThread: 12
          PendingThread:    0
          DeadThread:       4
          Hosted Runtime:   no
                                                                                   Lock  
                 ID OSID ThreadOBJ    State GC Mode     GC Alloc Context  Domain   Count Apt Exception
             0    1 16b007da908     26020 Preemptive  64EDD188:00000000 00823830 1     STA System.OutOfMemoryException 57b53d90
             2    2  af8 007e9dc8     2b220 Preemptive  00000000:00000000 007d4838 0     MTA (Finalizer) 
             3    3 1d94 0081af28     21220 Preemptive  00000000:00000000 007d4838 0     Ukn 
             5    6 2460772b960   102a220 Preemptive  00000000:00000000 007d4838 0     MTA (Threadpool Worker) 
             8   47 2772eebf038   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 object57b53d90
          Exception type:   System.OutOfMemoryException
          Message:          沒有足夠的內存繼續(xù)執(zhí)行程序。
          InnerException:   <none>
          StackTrace (generated):
              SP       IP       Function
              00482C80 6450BD46 mscorlib_ni!System.Runtime.InteropServices.Marshal.AllocHGlobal(IntPtr)+0xc2fdf6
              00482CB0 198DCEF2 UNKNOWN!FastReport.Export.TTF.TrueTypeCollection..ctor(System.Drawing.Font)+0xe2
              00482D00 198DCC0F UNKNOWN!FastReport.Export.TTF.ExportTTFFont.GetFontData()+0x47
              00482D58 198DAD54 UNKNOWN!FastReport.Export.Pdf.PDFExport.WriteFont(FastReport.Export.TTF.ExportTTFFont)+0xa4
              00483A7C 198D9CD5 UNKNOWN!FastReport.Export.Pdf.PDFExport.AddPDFFooter()+0x8d
              00483C38 198D9B53 UNKNOWN!FastReport.Export.Pdf.PDFExport.Finish()+0x23
              00483C80 19938119 UNKNOWN!FastReport.Export.ExportBase.Export(FastReport.Report, System.IO.Stream)+0x229
              00483CD8 19937A9D UNKNOWN!FastReport.Export.ExportBase.Export(FastReport.Report, System.String)+0x4d
              00483D08 19937A3D UNKNOWN!FastReport.Report.Export(FastReport.Export.ExportBase, System.String)+0xd
              00483D10 15D9FA39 UNKNOWN!xxxx.xxx.FormPrint.PrintPdf(Boolean, System.String, xxxx.DAL.xxx.DataObject.IPatinfoBase, Boolean, System.String)+0x359
              00483DF0 137B265A UNKNOWN!xxxx.UI.xxx.PrintOrdert2PDF.Handle(System.Object[])+0x3ca
              00483EB4 1178B36C xxx_PrintOrder2Pdf!xxxx.xxx.PrintOrder2Pdf.Form1.timer1_Tick(System.Object, System.EventArgs)+0xca4
              0048414117884DD UNKNOWN!System.Windows.Forms.Timer.OnTick(System.EventArgs)+0x15
              00484154 117883A0 UNKNOWN!System.Windows.Forms.Timer+TimerNativeWindow.WndProc(System.Windows.Forms.Message ByRef)+0x38
              00484160 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: none

          GC 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 調出所有線程的托管棧,突然我發(fā)現(xiàn)主線程有點奇怪,調用棧特別深,不信我截圖跟你看。

          從圖中可以看到,xxx.xxx.PrintOrder2Pdf.Form1.timer1_Tick 高達 133 個,這說明 Form 窗體上有一個 timer 沒有控制好,出現(xiàn)重復執(zhí)行的情況了,不管怎么說,這個地方肯定有問題,接下來要做的就是把這個 timer1_Tick 源碼導出來看看怎么寫的,還是用那個 !name2ee + !savemodule 老命令導出,代碼簡化如下。


          private void timer1_Tick(object sender, EventArgs e)
          {
           if (!IsContinue)
           {
            PrintMsg("等待上一掃描執(zhí)行完畢");
            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
              }
              IsContinue=true;
          }

          從代碼中可以看出,這個方法用了很多的 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();
                  }

          起碼這種 停止啟動 的方式肯定能規(guī)避timer的重復執(zhí)行,先把這個改了再說,給醫(yī)院那邊先部署上,再觀后效。。。

          三:總結

          朋友在五一節(jié)后,也就是前天給醫(yī)院部署上了,昨天反饋沒有再出現(xiàn)問題,截一張圖證明一下??????。

          大家應該也看的出來,其實我心里是沒底的。。。后續(xù)和朋友再溝通,發(fā)現(xiàn)了三點信息:

          • 醫(yī)生的電腦配置為 8G or 12G

          • 有時候為了一些便利,醫(yī)生會開雙進程

          • 還有更多其他模塊的內存溢出案例

          看了下程序是采用插件式編程,而且還用了 DevExpress + FastReport 這些重量級的組件,再搭配上醫(yī)生開的雙進程讓電腦余下的貧瘠內存更加吃緊,可能這才是程序在  1.2G 就分配不到非托管內存的深層原因,現(xiàn)場情況應該更復雜,只能先到這里了。

          建議措施如下,很簡單。

          • 增加電腦的配置,up 到 16G 最好了,畢竟甲方都不差錢 ??????







          回復 【關閉】
          回復 【實戰(zhàn)】獲取20套實戰(zhàn)源碼
          回復 【被刪】
          回復 【訪客】
          回復 【小程序】學獲取15套【入門+實戰(zhàn)+賺錢】小程序源碼
          回復 【python】學微獲取全套0基礎Python知識手冊
          回復 【2019】獲取2019 .NET 開發(fā)者峰會資料PPT
          回復 【加群】加入dotnet微信交流群

          輸入任意文字即可激活,這款軟件愛了!


          人人影視字幕組涼了,這款美劇APP不能錯過!



          瀏覽 44
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲无码毛片基地 | 亚洲三级电影网 | 91福利在线视频 | 精品的ai操逼网 精品豆花视频在线 | 狠狠干狠狠操 |