記一次 .NET 某電廠Web系統 記憶體泄漏分析

来源:https://www.cnblogs.com/huangxincheng/archive/2022/07/14/16476232.html
-Advertisement-
Play Games

一:背景 1. 講故事 前段時間有位朋友找到我,說他的程式記憶體占用比較大,尋求如何解決,截圖就不發了,分析下來我感覺除了程式本身的問題之外,.NET5 在記憶體管理方面做的也不夠好,所以有必要給大家分享一下。 二:WinDbg 分析 1. 托管還是非托管泄漏 這個還是老規矩 !address -sum ...


一:背景

1. 講故事

前段時間有位朋友找到我,說他的程式記憶體占用比較大,尋求如何解決,截圖就不發了,分析下來我感覺除了程式本身的問題之外,.NET5 在記憶體管理方面做的也不夠好,所以有必要給大家分享一下。

二:WinDbg 分析

1. 托管還是非托管泄漏

這個還是老規矩 !address -summary!eeheap -gc 組合命令排查一下。


0:000> !address -summary

                                     
Mapping file section regions...
Mapping module regions...
Mapping PEB regions...
Mapping TEB and stack regions...
Mapping heap regions...
Mapping page heap regions...
Mapping other regions...
Mapping stack trace database regions...
Mapping activation context regions...
--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE                                426     7df8`af1ce000 ( 125.971 TB)           98.42%
MEM_RESERVE                             619      206`01b9c000 (   2.023 TB)  99.75%    1.58%
MEM_COMMIT                             3096        1`4f286000 (   5.237 GB)   0.25%    0.00%

0:000> !eeheap -gc
Number of GC Heaps: 16
------------------------------
...
Heap 15 (0000024AF6BAA2E0)
generation 0 starts at 0x000002509729B538
generation 1 starts at 0x000002509720B638
generation 2 starts at 0x0000025096F91000
ephemeral segment allocation context: none
         segment             begin         allocated         committed    allocated size    committed size
0000025096F90000  0000025096F91000  000002509B5AFB40  000002509DFE9000  0x461eb40(73526080)  0x7058000(117800960)
Large object heap starts at 0x00000250D6F91000
         segment             begin         allocated         committed    allocated size    committed size
00000250D6F90000  00000250D6F91000  00000250DEB6AC60  00000250DEB6B000  0x7bd9c60(129866848)  0x7bda000(129867776)
Pinned object heap starts at 0x00000250E6F91000
00000250E6F90000  00000250E6F91000  00000250E75D94E0  00000250E75DA000  0x6484e0(6587616)  0x649000(6590464)
Allocated Heap Size:       Size: 0xc840c80 (209980544) bytes.
Committed Heap Size:       Size: 0xec32000 (247668736) bytes.
------------------------------
GC Allocated Heap Size:    Size: 0xd6904dd8 (3599781336) bytes.
GC Committed Heap Size:    Size: 0x11884b000 (4706316288) bytes.

從卦中指標看:5.2G4.7G ,很明顯問題出在了托管層,但如果你細心的話,你會發現這 4.7G 是 commit 記憶體,其實真正占用的只有 3.5G,言外之意有 1.2G 的空間其實屬於 Commit 區,也就是為了少向 OS 申請記憶體而虛占的一部分空間,畫個簡圖就像下麵這樣:

這也是我第一次看到 AllocCommit 差距有這麼大。

2. 探究托管記憶體占用

首先看下 3.5G 記憶體這塊,這個分析比較簡單,直接看托管堆就好了。


0:000> !dumpheap -stat
Statistics:
              MT    Count    TotalSize Class Name
...
00007ffa19e64808    25804     36125600 xxxx.MongoDB.Entity.GeneratorMongodb
0000024af68aa2c0    20517    630474976      Free
00007ffa1947bf30    52477    654558722 System.Byte[]
00007ffa194847f0     1921   1044818774 System.Char[]
00007ffa19437a90   673850   1116597742 System.String

從輸出信息看,主要還是被 String,Char[],Byte[] 占用了,根據經驗,這三個組合在一塊,大多是存了什麼位元組流在記憶體中,比如 PdfImage ,然後在記憶體中倒來倒去就成這個樣子了。

接下來在 char[] 中抽一些 obj 看一下,果然大多是 jpg


0:000> !DumpObj /d 00000250da9d3618
Name:        System.Char[]
MethodTable: 00007ffa194847f0
EEClass:     00007ffa19484770
Size:        11990052(0xb6f424) bytes
Array:       Rank 1, Number of elements 5995014, Type Char (Print Array)
Content:     data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAA4QAAASwCAYAAACjAoQOAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DA
Fields:
None
0:000> !DumpObj /d 00000250db542a60
Name:        System.Char[]
MethodTable: 00007ffa194847f0
EEClass:     00007ffa19484770
Size:        15667860(0xef1294) bytes
Array:       Rank 1, Number of elements 7833918, Type Char (Print Array)
Content:     data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAA4QAAASwCAYAAACjAoQOAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DA
Fields:
None

可以看到,3.2G 的記憶體大多是被 圖片 所占用,朋友反饋是把 圖片 存到資料庫所致,好了,這一塊就分析到這裡,分析思路也很明顯,接下來探究下 alloc 和 commit 的問題。

3. 為什麼 alloc 和 commit 差距這麼大

一般而言,差距大有以下幾點誘因所致。

  1. segment 越大,commit 預設的區域就越大

根據官方文檔的定義,segment 的大小取決於 cpu核數 和 程式的位數,截圖如下:

有了這個指標,怎麼到 dump去找各自數據呢,用 !eeversion 看下 heap 的個數以及觀察下記憶體地址的長度就好啦。


0:000> !eeversion
5.0.621.22011 free
5,0,621,22011 @Commit: 478b2f8c0e480665f6c52c95cd57830784dc9560
Server mode with 16 gc heaps
SOS Version: 6.0.5.7301 retail build

可以看到,這個程式是用 64bit 跑在 16 核機器上,segment 上限為 1G

  1. segment 越多,alloc 和 commit 累計差距就會越大

每個 segment 都差一點,那多個 segment 自然就累計出來了,接下來就找一下那些差距比較大的 segment。


Heap 0 (0000024AF685A500)
         segment             begin         allocated         committed    allocated size    committed size
0000024AF6F90000  0000024AF6F91000  0000024AF83B6D28  0000024AFEB42000  0x1425d28(21126440)  0x7bb1000(129699840)
------------------------------
Heap 1 (0000024AF68819A0)
         segment             begin         allocated         committed    allocated size    committed size
0000024B56F90000  0000024B56F91000  0000024B58507410  0000024B5D2E5000  0x1576410(22504464)  0x6354000(104153088)
------------------------------
Heap 4 (0000024AF688F770)
         segment             begin         allocated         committed    allocated size    committed size
0000024C76F90000  0000024C76F91000  0000024C783BDBE8  0000024C7ECF7000  0x142cbe8(21154792)  0x7d66000(131489792)
------------------------------
Heap 6 (0000024AF68980A0)
         segment             begin         allocated         committed    allocated size    committed size
0000024D36F90000  0000024D36F91000  0000024D38B87E78  0000024D3F881000  0x1bf6e78(29322872)  0x88f0000(143589376)
...

從輸出信息看,差距最大的是 Heap6,高達 110M,那這 110M 差距是否合理呢?其實仔細想想也不太離譜,畢竟命中了上面提到的兩點,但我覺得這裡的空間是不是還可以再智能的優化一下,再縮小一點?

4. Commit區能不能再小點?

能不能縮的再小一點,其實這是一種 CLR 智能演算法的抉擇,Commit 區越大,申請對象的速度就越快,向 OS 申請記憶體的頻率就越低,反之 Commit 區越小,向 OS 再次申請記憶體的概率就越大,段的模型圖大概是這個樣子:

後來仔細想了下,既然 Commit 區多保留了 110M,那曾經肯定是某一個時刻突破過,後來因為成了垃圾對象,被 GC 回收了,但記憶體區域被GC私藏下來,所以程式肯定出現過 快出快進 的現象,接下來的想法就是用 writemem 把 alloc ~ commit 的記憶體區間給導出來看下,是不是有什麼新發現。


0:000> .writemem D:\dumps\dump1\1.txt 0000024AF83B6D28 L?0x0678b2d8 
Writing 678b2d8 bytes.............

發現了很多類似這樣的信息,把這個信息提供給朋友後,朋友說他找到這塊問題了,是網站上用 NPOI 數據導出 功能所致。

三:總結

其實這個 dump 給了我們兩方面的教訓。

  1. 不要將 image 放到 sqlserver 里,不僅占用sql的資源,讓程式也不堪重負,畢竟讀出去都是 byte[] ...

  2. coreclr 雖然有自己的抉擇演算法,如果再智能一點就好了,讓 commit ~ alloc 之間的差距更合理一點。

圖片名稱
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 數組和結點這兩種數據結構之間的差異,決定了LinkedList相比ArrayList擁有更高的插入和刪除效率,而隨機訪問效率不如ArrayList。 transient transient只能用來修飾成員變數(field),被transient修飾的成員變數不參與序列化過程。 序列化: JVM中的J ...
  • MongoDB集群搭建 MongoDB集群簡介 mongodb 集群搭建的方式有三種: 主從備份(Master - Slave)模式,或者叫主從複製模式。 副本集(Replica Set)模式 分片(Sharding)模式 其中,第一種方式基本沒什麼意義,官方也不推薦這種方式搭建。另外兩種分別就是副 ...
  • 之前有一個同事突然我問了我一個問題,說在foreach當中能不能刪除list裡面的元素,我當時大概說了一下是否能刪除,以及原因;接下來我們來探討一下是否能夠如此; (1)遍歷元素 首先,我們一一段代碼為例: String[] array = {"1", "2", "3"}; for (String ...
  • 杭電oj 網站實時狀態 (hdu.edu.cn) 2032 楊輝三角 楊輝三角,是二項式繫數在三角形中的一種幾何排列,中國南宋數學家楊輝1261年所著的《詳解九章演算法》一書中出現。在歐洲,帕斯卡(1623 1662)在1654年發現這一規律,所以這個表又叫做帕斯卡三角形。帕斯卡的發現比楊輝要遲393 ...
  • 兄弟們,今天來實現一下用Python計算1到500的偶數總和,灰常簡單,檢驗一下大家基礎學的怎麼樣! 涉及到的知識點 range 使用 for 迴圈 推導式 函數調用 # 這應該都學過吧,如果剛剛接觸Python,基礎都還沒怎麼學的話,加Q群 279199867,領取2022最新的Python視頻教 ...
  • 場景 該設計多適用於MES,ERP,WMS 等管理類型的項目。 在做管理類型項目的時候,前端經常會使用到下拉框,比如:設備,工廠等等。下拉組件中一般只需要他們的ID,Name屬性,而這些數據都創建於其他模塊並存儲在資料庫中。 如圖: 寫一個設備的下拉組件的數據需要通過請求後端去獲取,如:localh ...
  • 一、問題描述 在使用BaGet進行包管理時中,上傳symbols產生400報錯: github action中報錯信息 supercisord中報錯信息 二、問題分析 控制臺中輸出的錯誤信息僅限於請求狀態碼400,想要找到詳細的報錯原因,我們需要從https://github.com/loic-sh ...
  • 1.添加引用 2.添加配置文件 /// <summary> /// 微信 /// </summary> public class WeChat { // 小程式 public string WxOpenAppId { get; set; } public string WxOpenAppSecret ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...