您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > .NET

记一次 .NET 某招聘网后端服务 内存暴涨分析

时间:2021-10-27 11:23:59  来源:  作者:一线码农的vlog

一:背景

1. 讲故事

前段时间有位朋友wx找到我,说他的程序存在内存阶段性暴涨,寻求如何解决,和朋友沟通下来,他的内存平时大概是5G 左右,在某些时点附近会暴涨到 10G+, 画个图大概就是这样。

记一次 .NET 某招聘网后端服务 内存暴涨分析

 

所以接下来就是想办法给他找到那莫名奇妙的 5-6G 是个啥,上 windbg 说话。

二:Windbg 分析

1. 判断托管还是非托管

从描述上看大概率是托管层面的问题,但为了文章的完整性,我们还是用 !address -summary 和 !eeheap -gc 来看一下。


0:000> !address -summary

--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free                                   1164      7f5`58f12000 (   7.958 TB)           99.48%
<unknown>                              6924        a`6de84000 (  41.717 GB)  97.90%    0.51%
Stack                                  1123        0`16340000 ( 355.250 MB)   0.81%    0.00%
Image                                  4063        0`1607d000 ( 352.488 MB)   0.81%    0.00%
Heap                                     71        0`0c9ea000 ( 201.914 MB)   0.46%    0.00%
TEB                                     374        0`002ec000 (   2.922 MB)   0.01%    0.00%
Other                                    13        0`001c6000 (   1.773 MB)   0.00%    0.00%
PEB                                       1        0`00001000 (   4.000 kB)   0.00%    0.00%

--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_PRIVATE                            5423        a`87200000 (  42.111 GB)  98.83%    0.51%
MEM_IMAGE                              7033        0`1e5d6000 ( 485.836 MB)   1.11%    0.01%
MEM_MAppED                              113        0`01908000 (  25.031 MB)   0.06%    0.00%

--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE                               1164      7f5`58f12000 (   7.958 TB)           99.48%
MEM_RESERVE                            4165        8`1b873000 (  32.430 GB)  76.11%    0.40%
MEM_COMMIT                             8404        2`8b86b000 (  10.180 GB)  23.89%    0.12%


0:000> !eeheap -gc
Number of GC Heaps: 32
------------------------------
Heap 0 (00000000004106d0)
generation 0 starts at 0x0000000082eb0e58
generation 1 starts at 0x0000000082d79b20
generation 2 starts at 0x000000007fff1000
ephemeral segment allocation context: none
         segment             begin         allocated              size
000000007fff0000  000000007fff1000  0000000083f80128  0x3f8f128(66646312)
Large object heap starts at 0x000000087fff1000
         segment             begin         allocated              size
000000087fff0000  000000087fff1000  0000000883fe4190  0x3ff3190(67056016)
0000000927ff0000  0000000927ff1000  000000092bfe2430  0x3ff1430(67048496)
0000000a81c50000  0000000a81c51000  0000000a8221c858  0x5cb858(6076504)
Heap Size:               Size: 0xc53ef40 (206827328) bytes.
------------------------------
...
Heap 31 (0000000019c84130)
generation 0 starts at 0x0000000844fc5170
generation 1 starts at 0x0000000844f851f8
generation 2 starts at 0x000000083fff1000
ephemeral segment allocation context: none
         segment             begin         allocated              size
000000083fff0000  000000083fff1000  0000000845171ca0  0x5180ca0(85462176)
Large object heap starts at 0x00000008fbff1000
         segment             begin         allocated              size
00000008fbff0000  00000008fbff1000  00000008fffe2290  0x3ff1290(67048080)
000000094bff0000  000000094bff1000  000000094ea2ebb8  0x2a3dbb8(44293048)
000000096bff0000  000000096bff1000  000000096dbdec00  0x1bedc00(29285376)
Heap Size:               Size: 0xd79d6e8 (226088680) bytes.
------------------------------
GC Heap Size:            Size: 0x1f1986a88 (8348265096) bytes.

从卦中得知,10G的内存,托管堆吃掉了 8.3G,很明显托管层问题,知道大方向后,接下来就可以到托管堆看一看,根据过往经验程序肯定是生成了大量的类对象所致,上命令 !dumpheap -stat 。


0:000> !dumpheap -stat
Statistics:
              MT    Count    TotalSize Class Name
...
000007fe9ddd5fc0   341280     30032640 System.ServiceModel.Description.MessagePartDescription
000007fe9c4865a0   866349     41584752 System.Xml.XmlDictionaryString
000007fe9defb098   937801     45014448 System.Xml.XmlDictionaryString
000007fe9c66bd28   105052     45086880 System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[System.Xml.XmlDictionaryString, System.Runtime.Serialization]][]
000007fe9e0f4d20   113299     49050864 System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[System.Xml.XmlDictionaryString, System.Runtime.Serialization]][]
00000000003c9190    44573    618414438      Free
000007fef8f6c168   428410   1209974642 System.Char[]
000007fef8f4f1b8  2849758   1246912848 System.Object[]
000007fef8f6f058   531963   1670620873 System.Byte[]
000007fef8f6aee0  2368431   2382587716 System.String

真是皂滑弄人,并没有命中过往经验,可以看出占用最大的都是些 Byte,String,Char,Object 基础类型,其实这些基础类型排查起来很难搞,要么不断的用 -min, -max 去筛选,要么就写一个脚本对它进行分组排序,蹩脚脚本如下:


"use strict";

/*
   按 mt 对托管堆类型的size进行分组
*/

let platform = 64
let mtlist = ["000007fef8f4f1b8"];
let maxlimit = 100;

function initializeScript() { return [new host.apiVersionSupport(1, 7)]; }
function log(str) { host.diagnostics.debugLog(str + "n"); }
function exec(str) { log("n" + str); return host.namespace.Debugger.Utility.Control.ExecuteCommand(str); }
function invokeScript() { for (var mt of mtlist) { groupby_mtsize_inheap(mt); } }

//对某个类型按照size 进行分组
function groupby_mtsize_inheap(mt) {
    var size_group = {};
    var commandText = "!dumpheap -mt " + mt;
    var output = exec(commandText);
    for (var line of output) {
        if (line == "" || line.indexOf("Address") > -1) continue;
        if (line.indexOf("Statistics") > -1) break;
        var size = parseInt(line.substring(Math.ceil(platform / 2) + 1).trim());

        if (!size_group[size]) size_group[size] = 0;

        size_group[size]++;
    }
    show_top10_format(mt, size_group);
}

function show_top10_format(mt, size_group) {
    var maparr = [];

    //转数组
    for (var size in size_group) {
        maparr.push({ "size": size, "count": size_group[size], "totalsize": (size * size_group[size]) });
    }

    maparr.sort(function (a, b) { return b.totalsize - a.totalsize });

    var topTotalSize = 0;

    //按size输出
    for (var i = 0; i < Math.min(maparr.length, maxlimit); i++) {
        var size = maparr[i].size;
        var count = maparr[i].count;
        var totalsize = Math.round(maparr[i].totalsize / 1024 / 1024, 2);

        topTotalSize += totalsize

        log("size=" + size + ",count=" + count + ",totalsize=" + totalsize + "M");
    }

    log("Total:" + topTotalSize + "M");

    //show max
    if (maparr.length > 0) {
        var size = maparr[0].size;
        var totalsize = Math.round(maparr[0].totalsize / 1024 / 1024, 2) + "M";
        var output = exec("!dumpheap -mt " + mt + " -min 0n" + size + " -max 0n" + size + " -short").Take(maxlimit);
        for (var line of output) {
            log(line);
        }
    }
}


接下来把 string 的方法表地址传下去看看排序结果,简化输出如下:


!dumpheap -mt 000007fef8f6aee0
size=29285946,count=2,totalsize=56M
size=29285540,count=2,totalsize=56M
size=29285502,count=2,totalsize=56M
size=29285348,count=2,totalsize=56M
size=27455186,count=2,totalsize=52M
size=31116504,count=1,totalsize=30M
size=31116490,count=1,totalsize=30M
size=31116306,count=1,totalsize=30M
size=31115934,count=1,totalsize=30M
size=31115920,count=1,totalsize=30M
size=31115718,count=1,totalsize=30M
size=29286342,count=1,totalsize=28M
size=29285898,count=1,totalsize=28M
...
Total:1198M

可以看到,有不少大 size 的 string,那这些string到底是个啥,这里我随便抽几个导出到txt看看。


0:000> !dumpheap -mt 000007fef8f6aee0 -min 0n31116490 -max 0n31116490 -short 
0000000a61c51000
0:000> !do 0000000a61c51000 
Name:        System.String
MethodTable: 000007fef8f6aee0
EEClass:     000007fef88d3720
Size:        31116490(0x1daccca) bytes
File:        C:windowsMicrosoft.NETassemblyGAC_64mscorlibv4.0_4.0.0.0__b77a5c561934e089mscorlib.dll
String:      <String is invalid or too large to print>

Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fef8f6dc90  40000aa        8         System.Int32  1 instance         15558232 m_stringLength
000007fef8f6c1c8  40000ab        c          System.Char  1 instance               50 m_firstChar
000007fef8f6aee0  40000ac       18        System.String  0   shared           static Empty
                                 >> Domain:Value  00000000003fb620:NotInit  000000001ca30bd0:NotInit  000000001f7b21a0:NotInit  000000001f8940c0:NotInit  0000000027dc46b0:NotInit  00000000281bd720:NotInit  00000000282b7ee0:NotInit  <<

0:000> .writemem D:dumpsxxxxstring.txt 0000000a61c51000 L?0x1daccca
Writing 1daccca bytes..........

记一次 .NET 某招聘网后端服务 内存暴涨分析

 

从内容看其实就是 pdf 的 base64 编码,以同样的方式调研 char[] 和 byte[] 类型,发现大多也都是 pdf,猜测程序在处理 pdf 的过程中,进行了 byte[],char[],string 之间的切换,所以这些对象理论上大多属于无根对象,其实通过 !heapstat -iu 也能看到那大约 5.5G 的无根对象正等待GC回收。


0:000> !heapstat -iu										
Heap             Gen0         Gen1         Gen2          LOH										
Heap0        17625808      1274680     47745824    140181016										
...									
Total       357486256     28100616   2229673376   5733004848										
										
Free space:                                                 Percentage										
Heap0         3962240           24     11211224       298616SOH: 22% LOH:  0%										
Heap1         5625856          144      9857168       302152SOH: 27% LOH:  0%										
...									
Heap31        1448576           24     19957312       218024SOH: 25% LOH:  0%										
Total       181492784         1136    431825856      5183128										
										
Unrooted objects:                                           Percentage										
Heap0        12163928       243584        42872    137153536SOH: 18% LOH: 97%										
...									
Heap31         236832       239272      1435840    139770656SOH:  2% LOH: 99%										
Total       164954952      7948448     29066480   5530423784										

三:总结

本次内存阶段性暴涨的事故,主要还是程序接收了上游过多的 pdf文件,毕竟这些都是大对象,还进行了 char[] ,string,byte[] 的切换,造成短时间内过大的内存占用。

最后就是我个人的解决建议:

  1. 针对大量的pdf,能否借用第三方的 oss 软件来规避一些不必要的内存占用。
  2. 清洗服务是否可以做些限流或者使用服务均摊的方式。

后来听朋友说,他做了筛选过滤以及一些业务流程优化解决了这个问题,我想现实中肯定有很多朋友遇到过这类问题,欢迎大家留言补充您的解决方案。



Tags: .NET   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
编译和反编译.NET 中的编译是把开发人员写的 C# 代码转化为计算机可理解的代码的过程,也就是中间语言代码(IL代码)。在这个过程中,C# 源代码被转换为可执行文件(exe或者dll 文件)...【详细内容】
2022-07-15  Tags: .NET  点击:(1)  评论:(0)  加入收藏
前言由于客户网络安全限制,连接到互联网的设备不能访问内网。需要先从客户端应用中导出数据到文件,再将文件复制到U盘,最后通过内网机器上传数据。如何保证,在复制、传输过程中,...【详细内容】
2022-03-22  Tags: .NET  点击:(125)  评论:(0)  加入收藏
一:背景1. 讲故事前段时间有位朋友wx找到我,说他的程序存在内存阶段性暴涨,寻求如何解决,和朋友沟通下来,他的内存平时大概是5G 左右,在某些时点附近会暴涨到 10G+, 画个图大概就...【详细内容】
2021-10-27  Tags: .NET  点击:(129)  评论:(0)  加入收藏
大型 .NET 应用程序中的内存问题是某种无声的杀手。有点像高血压。你可以长期吃垃圾食品而忽略它,直到有一天你面临严重的问题。对于 .NET 程序,该严重问题可能是高内存消耗、...【详细内容】
2021-07-15  Tags: .NET  点击:(177)  评论:(0)  加入收藏
我们总是能听到一些人说,.NET 不行、学 .NET 没发展前途之类的言论,有的童鞋听多了便也开始怀疑自己选择的 .NET 方向是不是错了。不得不承认,在 .NET 没有实现跨平台之前,市场...【详细内容】
2021-07-15  Tags: .NET  点击:(190)  评论:(0)  加入收藏
自微软宣布 .NET 5 平台消息之后, 相关的快速开发框架 就如 雨后春笋 般的多了起来,众所周知,框架好不好,其 wiki 真的非常重要,好的 wiki 能让人 更加快速 的上手,并体验 起来 F...【详细内容】
2021-04-06  Tags: .NET  点击:(483)  评论:(0)  加入收藏
在渗透测试中,有些工具的运行(例如高版本的Powershell)需要依赖Microsoft .NET Framework 4.0的环境。 而默认配置下,Win7不支持Microsoft .NET Framework 4.0。为了保证工具...【详细内容】
2020-07-03  Tags: .NET  点击:(182)  评论:(0)  加入收藏
使用vs创建.net core 控制台项目 创建完成,点击发布 配置linux运行时,配置完成后发布。 将发布的文件放置到linux服务器上 在linux服务器上 安装 dotnet运行环境在linux服务器...【详细内容】
2020-06-30  Tags: .NET  点击:(619)  评论:(0)  加入收藏
▌简易百科推荐
编译和反编译.NET 中的编译是把开发人员写的 C# 代码转化为计算机可理解的代码的过程,也就是中间语言代码(IL代码)。在这个过程中,C# 源代码被转换为可执行文件(exe或者dll 文件)...【详细内容】
2022-07-15  IT狂人日记    Tags:.NET   点击:(1)  评论:(0)  加入收藏
我们在开发 webapi 项目时如果遇到 api 接口需要同时支持多个版本的时候,比如接口修改了入参之后但是又希望支持老版本的前端(这里的前端可能是网页,可能是app,小程序 等等)进行...【详细内容】
2022-07-14  IT技术资源爱好者    Tags:.Net   点击:(3)  评论:(0)  加入收藏
什么是.NET.NET 是由 Microsoft 创建的开源开发平台,用于生成多种不同类型的应用程序,主要支持C#、F#及VB。.NET程序运行原理.NET程序的运行是由其虚拟机CLR(公共语言运行时)把...【详细内容】
2022-06-21  威步上海    Tags:.NET   点击:(26)  评论:(0)  加入收藏
Asp.Net Core Identity 是.Net自带的身份认证系统,支持用户界面 (UI) 登录功能,并且管理用户、密码、配置文件数据、角色、声明、令牌、电子邮件确认等等。使用Visual Studio...【详细内容】
2022-06-05  海椰人  博客园  Tags:.Net   点击:(35)  评论:(0)  加入收藏
安装Hangfire新建ASP.NET Core空 项目,.Net Core版本3.1 往*.csproj添加包引用,添加新的PackageReference标记。如下所示。请注意,下面代码段中的版本可能已经过时,如有需要,请使...【详细内容】
2022-05-07  壮志林云    Tags:.NET   点击:(76)  评论:(0)  加入收藏
 B/S架构的Web程序几乎占据了应用软件的绝大多数市场,但是C/S架构的WinForm、WPF客户端程序依然具有很实用的价值,如设计类软件 AutoCAD与Autodesk Revit、WPS、IT类的集成开...【详细内容】
2022-04-27  IT技术资源爱好者  博客园  Tags:.NET   点击:(153)  评论:(0)  加入收藏
前几天有个老项目找到我,有多老呢?比我工作年限都长,见到这个项目我还得叫一声前辈。这个项目目前使用非常稳定,十多年了没怎么更新过,现在客户想加一个小功能:在线预览Word文档。...【详细内容】
2022-04-27  海椰人  博客园  Tags:.Net   点击:(65)  评论:(0)  加入收藏
之前,我们已经了解了ASP.NET Core中的身份认证,现在,我们来聊一下授权。老规矩,示例程序源码XXTk.Auth.Samples已经提交了,需要的请自取。概述ASP.NET Core中的授权方式有很多,我...【详细内容】
2022-04-20  日行四善  博客园  Tags:授权   点击:(143)  评论:(0)  加入收藏
序言本文将分别介绍 Authentication(认证) 和 Authorization(授权)。并以简单的例子在 ASP.NET Core 6.0 的 WebAPI 中分别实现这两个功能。 相关名词Authentication 和 Author...【详细内容】
2022-04-18  芦荟柚子茶  博客园  Tags:ASP.NET   点击:(197)  评论:(0)  加入收藏
前言由于客户网络安全限制,连接到互联网的设备不能访问内网。需要先从客户端应用中导出数据到文件,再将文件复制到U盘,最后通过内网机器上传数据。如何保证,在复制、传输过程中,...【详细内容】
2022-03-22  My IO    Tags:.NET Core   点击:(125)  评论:(0)  加入收藏
站内最新
站内热门
站内头条