1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 13826字,需瀏覽 28分鐘

         ·

        2021-05-16 10:58

        一:背景

        1. 講故事

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

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

        廢話不多說(shuō),上 windbg,看能不能先救個(gè)急。

        二:windbg 分析

        1. 找出異常對(duì)象

        如果內(nèi)存溢出了,大家應(yīng)該知道 C# 會(huì)拋一個(gè) OutOfMemoryException 異常,而且還會(huì)附加到那個(gè)執(zhí)行線程上,所以先用 !t 命令調(diào)出當(dāng)前的所有托管線程。


        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號(hào) 線程果然帶了一個(gè) System.OutOfMemoryException,接下來(lái)用 !pe 查查這個(gè)異常的調(diào)用棧信息。


        0:000> !pe 57b53d90
        Exception object57b53d90
        Exception type:   System.OutOfMemoryException
        Message:          沒(méi)有足夠的內(nèi)存繼續(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

        從上面的調(diào)用??梢钥闯觯菜瞥绦蚴窃谧鲆粋€(gè) pdf 打印,最后在 Marshal.AllocHGlobal 上拋了異常,熟悉這個(gè)方法的朋友應(yīng)該知道,它就是用來(lái)分配 非托管內(nèi)存 的。。。情況貌似有點(diǎn)不妙。??????

        接下來(lái)用 ILSpy 查一下 AllocHGlobal 方法的源碼,看看有什么可挖掘的地方。

        從圖中源碼邏輯可以看出,一旦非托管內(nèi)存分配失敗,托管層上手工拋出 OutOfMemoryException 異常,我去,這難道是非托管內(nèi)存溢出啦???

        2. 真的是非托管溢出了嗎?

        要鑒別是否為非托管堆出的問(wèn)題,還是用那個(gè)老辦法,看看 MEM_COMMIT Size ≈ GC Heap Size  即可。

        • !address -summary 查看進(jìn)程的內(nèi)存使用量

        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 指標(biāo)可以看出內(nèi)存使用量為 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。

        也就是說(shuō)大概 800M 不知道哪里去了,看似有點(diǎn)嚇人,其實(shí)算算也還可以,這里我稍微補(bǔ)充一下,看下面的公式:

        MEM_COMMIT (1.28G) = Image (228M) + Heap (69M) + Stack (18M) + GCHeap(450M) + GCLoader (153M) + else = 918M

        從上面列出來(lái)的信息可以看出,最后累積出的 918M 和 內(nèi)存使用量 1.28G 差不了多少,有些朋友可能要問(wèn), 這個(gè) GCLoader 怎么算出來(lái)的,很簡(jiǎn)單,它是 CLR 的加載堆,使用 !eeheap -loader 即可。


        0:000> !eeheap -loader
        --------------------------------------
        Total LoaderHeap size:   Size: 0x995a000 (160800768) bytes total, 0x13e000 (1302528) bytes wasted.
        =======================================

        到這里,我陷入了僵局??????,才 1.28G 的內(nèi)存占用,怎么就會(huì)把程序給弄溢出了?既然內(nèi)存上看不出問(wèn)題,那就從線程上入手吧,看看他們都在做什么?

        3. 查看每個(gè)線程都在做什么?

        要想看線程,可以用 ~*e !clrstack 調(diào)出所有線程的托管棧,突然我發(fā)現(xiàn)主線程有點(diǎn)奇怪,調(diào)用棧特別深,不信我截圖跟你看。

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


        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;
        }

        從代碼中可以看出,這個(gè)方法用了很多的 IsContinue 來(lái)踢掉重復(fù)請(qǐng)求,但最終還是出了bug,導(dǎo)致無(wú)限量遞歸,跟朋友溝通后建議用 Stop()Start() 來(lái)處理,參考如下代碼:


                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();
                }

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

        三:總結(jié)

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

        大家應(yīng)該也看的出來(lái),其實(shí)我心里是沒(méi)底的。。。后續(xù)和朋友再溝通,發(fā)現(xiàn)了三點(diǎn)信息:

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

        • 有時(shí)候?yàn)榱艘恍┍憷?,醫(yī)生會(huì)開(kāi)雙進(jìn)程

        • 還有更多其他模塊的內(nèi)存溢出案例

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

        建議措施如下,很簡(jiǎn)單。

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







        回復(fù) 【關(guān)閉】學(xué)關(guān)
        回復(fù) 【實(shí)戰(zhàn)】獲取20套實(shí)戰(zhàn)源碼
        回復(fù) 【被刪】學(xué)個(gè)
        回復(fù) 【訪客】學(xué)
        回復(fù) 【小程序】學(xué)獲取15套【入門+實(shí)戰(zhàn)+賺錢】小程序源碼
        回復(fù) 【python】學(xué)微獲取全套0基礎(chǔ)Python知識(shí)手冊(cè)
        回復(fù) 【2019】獲取2019 .NET 開(kāi)發(fā)者峰會(huì)資料PPT
        回復(fù) 【加群】加入dotnet微信交流群

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


        人人影視字幕組涼了,這款美劇APP不能錯(cuò)過(guò)!



        瀏覽 44
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美性猛交xxxx乱大交游戏 | 波多野结衣伦理电影在线观看 | 91蝌蚪熟女少妇嗷嗷叫 | 欧美疯狂三p群体交乱小说 | 爱田奈々侵犯jux系列破坏版 | 欧美黄色片网站 | 久草福利资源网 | 国产精品久久久久久久久 | 女绳奴紧缚调教俱乐部 | 黄色视频在线网站 |