記一次 .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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...