一:背景

1. 讲故事

分享了几篇崩溃,这一篇跟大家分享一例内存暴涨,训练营里有位学员朋友找到我,说他们的软件存在内存暴涨,让我帮忙看下怎么回事,dump也抓到了。

二:内存暴涨分析

1. 内存真的暴涨吗

一直都给训练营里的学员灌输一个思想,就是不要相信任何人说的话,而应该是以数据说话,由于是 linux dump,可以使用 !maddress -summary 命令观察。


0:000> !maddress -summary
+-------------------------------------------------------------------------+
| Memory Type | Count | Size | Size (bytes) |
+-------------------------------------------------------------------------+
| GCHeap | 12 | 2.39gb | 2,565,107,712 |
| Stack | 32 | 237.33mb | 248,860,672 |
| Image | 1,380 | 200.21mb | 209,934,848 |
| PAGE_READWRITE | 83 | 121.45mb | 127,348,736 |
| LowFrequencyHeap | 208 | 13.89mb | 14,561,280 |
| HighFrequencyHeap | 146 | 9.10mb | 9,539,584 |
| LoaderCodeHeap | 9 | 9.00mb | 9,437,184 |
| HostCodeHeap | 6 | 360.00kb | 368,640 |
| ResolveHeap | 1 | 348.00kb | 356,352 |
| DispatchHeap | 1 | 196.00kb | 200,704 |
| PAGE_READONLY | 84 | 175.50kb | 179,712 |
| CacheEntryHeap | 2 | 100.00kb | 102,400 |
| IndirectionCellHeap | 2 | 88.00kb | 90,112 |
| LookupHeap | 2 | 80.00kb | 81,920 |
| StubHeap | 1 | 12.00kb | 12,288 |
| PAGE_EXECUTE_WRITECOPY | 2 | 8.00kb | 8,192 |
| PAGE_EXECUTE_READ | 1 | 4.00kb | 4,096 |
+-------------------------------------------------------------------------+
| [TOTAL] | 1,972 | 2.97gb | 3,186,194,432 |
+-------------------------------------------------------------------------+

从卦象看,总计 2.97G 的总内存,托管堆就吃了 2.39G,也就表示当前存在托管内存泄露,接下来的关注点就是托管堆了。

2. 托管堆暴涨分析

要想观察托管堆中到底有什么,可以使用 !dumpheap -stat 观察详情,输出如下:


0:000> !dumpheap -stat
Statistics:
MT Count TotalSize Class Name
...
7f5640cb1168 115,010 71,585,040 System.Collections.Generic.Dictionary<System.String, Newtonsoft.Json.Linq.JToken>+Entry[]
7f5640ae64f8 629,539 75,544,680 xxx.TraceContext+TraceScope
7f5640937348 2,100,061 134,403,904 Newtonsoft.Json.Linq.JValue
7f563b060f90 1,945,658 205,832,084 System.String
7f5640c8d660 2,118,456 220,319,424 Newtonsoft.Json.Linq.JProperty
7f563b0614c0 101,757 1,047,579,498 System.Byte[]
Total 18,591,667 objects, 2,539,831,376 bytes

从卦中看托管堆中有 10w 个 System.Byte[] ,大概吃了一半的托管堆内存,从经验上来说,这里面肯定有不少大的 Byte[],接下来做个简单的过滤,发现有2.8w 的 size=0n32792 的 Byte[] 数组,简化后如下:


0:000> !dumpheap -mt 7f563b0614c0 -min 0x8018 -max 0x8018
Address MT Size
7f552fc394a0 7f563b0614c0 32,792
7f552fc48858 7f563b0614c0 32,792
7f552fc52590 7f563b0614c0 32,792
7f552fc5e500 7f563b0614c0 32,792
7f552fc77fa0 7f563b0614c0 32,792
7f552fc834d0 7f563b0614c0 32,792
7f552fc8b718 7f563b0614c0 32,792
7f552fc93730 7f563b0614c0 32,792
...
7f5623f3f9a0 7f563b0614c0 32,792
7f5623f912e0 7f563b0614c0 32,792
7f5623f9b0a8 7f563b0614c0 32,792
7f5623fa5ee0 7f563b0614c0 32,792 Statistics:
MT Count TotalSize Class Name
7f563b0614c0 28,563 936,637,896 System.Byte[]
Total 28,563 objects, 936,637,896 bytes

那为什么有 2.8w 的 byte[] 得不到GC回收呢?随机抽几个使用 !gcroot 命令观察,参考如下:


0:000> !gcroot 7f55c7a6cf90
r15:
-> 7f5567484b08 System.Threading.ThreadPoolWorkQueueThreadLocals
-> 7f5567484b40 System.Threading.Thread
-> 7f55bb5d7d58 System.Threading.ExecutionContext
-> 7f55bb5d7ce8 System.Threading.AsyncLocalValueMap+MultiElementAsyncLocalValueMap
-> 7f55bb5d7d00 System.Collections.Generic.KeyValuePair<System.Threading.IAsyncLocal, System.Object>[]
-> 7f55bb5d7b98 xxxt+TraceScope
-> 7f555658d478 Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope
-> 7f56140ea038 Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine
-> 7f56140ea0c8 Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope
-> 7f56140f7160 System.Collections.Generic.List<System.IDisposable>
-> 7f561412fd80 System.IDisposable[]
...
-> 7f561434b6f8 xxx.RequestTrace.AspNetTraceContext
...
-> 7f561406ef10 Microsoft.Extensions.Configuration.ConfigurationRoot
-> 7f561406ef30 Microsoft.Extensions.Configuration.ConfigurationReloadToken
-> 7f561406ef48 System.Threading.CancellationTokenSource
-> 7f56140f50a0 System.Threading.CancellationTokenSource+CallbackPartition[]
-> 7f56142b1d38 System.Threading.CancellationTokenSource+CallbackPartition
-> 7f55cfe26900 System.Threading.CancellationTokenSource+CallbackNode
-> 7f55a585a858 System.Threading.CancellationTokenSource+CallbackNode
...
-> 7f55a59b4848 System.Threading.CancellationTokenSource+CallbackNode
-> 7f55a59b4178 System.Threading.ExecutionContext
-> 7f55a59b4108 System.Threading.AsyncLocalValueMap+MultiElementAsyncLocalValueMap
-> 7f55a59b4120 System.Collections.Generic.KeyValuePair<System.Threading.IAsyncLocal, System.Object>[]
-> 7f55a59b3bb0 xxx.RequestTrace.AspNetTraceContext
-> 7f55a59b3708 Microsoft.AspNetCore.Http.DefaultHttpContext
-> 7f55a59b2308 Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1Connection
-> 7f55a59b4b68 Microsoft.AspNetCore.WebUtilities.FileBufferingReadStream
-> 7f55a5b219b0 System.Byte[]

通过卦中的输出,很快就发现有超多的 CallbackNode 节点,熟悉这个类的朋友应该知道,它是你通过 CancellationTokenSource.Token.Register 注册的回调函数,在底层的话可以观察首节点的 Id=17812 即可,参考如下:


0:000> !do 7f55cfe26900
Name: System.Threading.CancellationTokenSource+CallbackNode
MethodTable: 00007f56405c1c18
EEClass: 00007f56405aa078
Size: 80(0x50) bytes
File: /usr/share/dotnet/shared/Microsoft.NETCore.App/3.1.32/System.Private.CoreLib.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007f56405c2690 4000705 8 ...CallbackPartition 0 instance 00007f56142b1d38 Partition
00007f56405c1c18 4000706 10 ...urce+CallbackNode 0 instance 0000000000000000 Prev
00007f56405c1c18 4000707 18 ...urce+CallbackNode 0 instance 00007f55cc695010 Next
00007f563b05b468 4000708 40 System.Int64 1 instance 17812 Id
00007f56405b55b8 4000709 20 ...Private.CoreLib]] 0 instance 00007f55cfe268c0 Callback
00007f563a5dc7a8 400070a 28 System.Object 0 instance 00007f561406ef10 CallbackState
00007f563b0f8fe0 400070b 30 ....ExecutionContext 0 instance 00007f55cfe23e88 ExecutionContext
00007f563b0f91d0 400070c 38 ...ronizationContext 0 instance 0000000000000000 SynchronizationContext

从卦中看到这个单链表已经有17812个节点,正常情况下不会有w级别的注册函数,这是一种失控的状态。

3. 为什么Register失控了

要想找到这个答案,可以深挖 Callback 和 CallbackState 两个字段,参考如下:


0:000> !dumpdelegate 00007f55cfe268c0
Target Method Name
00007f55cfe26788 00007f5640ae1040 xxx.ConfigurationExtensions+<>c__DisplayClass18_0.<OnChange>g__Callback|0(System.Object) 0:000> !DumpObj /d 00007f561406ef10
Name: Microsoft.Extensions.Configuration.ConfigurationRoot
MethodTable: 00007f56407bd910
EEClass: 00007f564079f808
Size: 32(0x20) bytes
File: /data/apps/xxx/Microsoft.Extensions.Configuration.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007f56407bd3a0 400000a 8 ...on.Abstractions]] 0 instance 00007f561406ece0 _providers
00007f56407be120 400000b 10 ...rationReloadToken 0 instance 00007f561406ef30 _changeToken

从卦象来看是和 IConfiguration 相关的变更令牌有关,比如对配置文件的监视,由于这些都是底层的东西,看样子是高层使用者用的不对。

那到底是哪里的不对呢?一时也没搞清楚,接下来回头观察引用链,发现有用户代码 TraceScopeAspNetTraceContext,赶紧研究相关代码,发现有相关的 Register 操作。

并且在托管堆上也发现了3w多的 AspNetTraceContext 和 62w 的TraceScope,这也是一种失控状态,输出如下:


...
7f5640af9a70 31,949 4,345,064 xxx.RequestTrace.AspNetTraceContext
...
7f5640ae64f8 629,539 75,544,680 xxx.TraceContext+TraceScope
...
7f563b0614c0 101,757 1,047,579,498 System.Byte[]

最后发现它是一个 HttpTrace的中间件,公司的架构组做的,然后就是告诉朋友重点关注对 AspNetTraceContext 和 TraceScope 相关的注册代码,或者直接将这个中间件剔除,观察下效果。

三:总结

这篇文章需要你对 CancellationToken 和 IChangeToken 有一定的了解,调试工作者难呀,不管是开源的还是非开源的,若要解决问题,需要对这些东西的底层有一个较微观的认识。。。否则如何给出指导意见呢!

聊一聊 .NET 某跨境物流系统 内存暴涨分析的更多相关文章

  1. 记一次 .NET 某三甲医院HIS系统 内存暴涨分析

    一:背景 1. 讲故事 前几天有位朋友加wx说他的程序遭遇了内存暴涨,求助如何分析? 和这位朋友聊下来,这个dump也是取自一个HIS系统,如朋友所说我这真的是和医院杠上了,这样也好,给自己攒点资源, ...

  2. 记一次 .NET 某WMS仓储打单系统 内存暴涨分析

    一:背景 1. 讲故事 七月中旬有一位朋友加wx求助,他的程序在生产上跑着跑着内存就飙起来了,貌似没有回头的趋势,询问如何解决,截图如下: 和这位朋友聊下来,感觉像是自己在小县城当了个小老板,规律的生 ...

  3. 记一次 .NET某家装ERP系统 内存暴涨分析

    一:背景 1. 讲故事 前段时间微信上有一位老朋友找到我,说他的程序跑着跑着内存会突然爆高,有时候会下去,有什么会下不去,怀疑是不是某些情况下存在内存泄露,让我帮忙分析一下,其实内存泄露方面的问题还是 ...

  4. 记一次 .NET 某外贸ERP 内存暴涨分析

    一:背景 1. 讲故事 上周有位朋友找到我,说他的 API 被多次调用后出现了内存暴涨,让我帮忙看下是怎么回事?看样子是有些担心,但也不是特别担心,那既然找到我,就给他分析一下吧. 二:WinDbg ...

  5. 记一次 WinDbg 分析 .NET 某工厂MES系统 内存泄漏分析

    一:背景 1. 讲故事 上个月有位朋友加微信求助,说他的程序跑着跑着就内存爆掉了,寻求如何解决,截图如下: 从聊天内容看,这位朋友压力还是蛮大的,话说这貌似是我分析的第三个 MES 系统了,看样子 . ...

  6. 记一次 .NET 医院CIS系统 内存溢出分析

    一:背景 1. 讲故事 前几天有位朋友加wx求助说他的程序最近总是出现内存溢出,很崩溃,如下图: 和这位朋友聊下来,发现他也是搞医疗的,哈哈,.NET 在医疗方面还是很有市场的,不过对于内存方面出的问 ...

  7. 记一次 .NET 某智能服装智造系统 内存泄漏分析

    一:背景 1. 讲故事 上个月有位朋友找到我,说他的程序出现了内存泄漏,不知道如何进一步分析,截图如下: 朋友这段话已经说的非常言简意赅了,那就上 windbg 说话吧. 二:Windbg 分析 1. ...

  8. 记一次 .NET 某电厂Web系统 内存泄漏分析

    一:背景 1. 讲故事 前段时间有位朋友找到我,说他的程序内存占用比较大,寻求如何解决,截图就不发了,分析下来我感觉除了程序本身的问题之外,.NET5 在内存管理方面做的也不够好,所以有必要给大家分享 ...

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

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

  10. 记一次 .NET医疗布草API程序 内存暴涨分析

    一:背景 1. 讲故事 我在年前写过一篇关于CPU爆高的分析文章 再记一次 应用服务器 CPU 暴高事故分析 ,当时是给同济做项目升级,看过那篇文章的朋友应该知道,最后的结论是运维人员错误的将 IIS ...

随机推荐

  1. RBAC权限模型如何让API访问控制既安全又灵活?

    扫描二维码 关注或者微信搜一搜:编程智域 前端至全栈交流与成长 发现1000+提升效率与开发的AI工具和实用程序:https://tools.cmdragon.cn/ 第四章:访问控制体系 1. RB ...

  2. VKProxy新增CORS设置和http响应缓存

    VKProxy 是使用c#开发的基于 Kestrel 实现 L4/L7的代理(感兴趣的同学烦请点个github小赞赞呢) 目前新添加了如下功能 http响应缓存 Memory Disk Redis C ...

  3. 导轨式串口服务器将ModbusTCP网口设备连接云端

    1.概述 我司在ModbusRTU转JSON的应用上满足了很多客户串口设备上云的需求,但是Modbus协议并不是只存在于串口设备,很多ModbusTCP网口只是走TCP/IP传输,也就是ModbusT ...

  4. prettier继承

    不支持extends prettier配置并不支持 extends 关键字,但是我们可以手动合并,比方说我们下载了一个配置包 @repo/config-prettier,我们可以如下继承到自己项目中 ...

  5. openWrt使用rclone挂载webDav

    前言 觉得路由器(linux)硬盘太小,又不好扩展(x86机器可以插硬盘.但arm机器的硬盘是焊死的无法扩展). 这个时候,我们可以通过davfs或者rclone将外部资源如webDav挂载到本机上用 ...

  6. GAMES 103 动画基础作业1 Shape Matching 浅浅解析

    简介 作业1简单实现了一个以一定初始速度和角速度的模型和墙壁碰撞的效果. 总共讲解了三种算法 impulse (脉冲法) Shape Matching(基于形状保持的算法, 不包含物理特性) Pena ...

  7. Games102 作业进行时 作业1 实现并分析四种拟合算法 TODO

    简介 由于已经过了上课时间, 不过工程上有对于拟合的使用, 现学现用. TODO

  8. Product-Mechanics: 金属机械加工(都有全自动的机床): 冲压+弯折+钣金+喷涂 | Plasma Cutting/Melting Machine(等离子切割/焊接机)

    精密机械有限公司拥有: 精密自动数控车床.精密高速冲床.自动高速冷锻打头机.精密CNC数控铣床. 慢/快走丝线切割.精密磨床.铣床.摇臂钻床等精密加工生产设备. 目前的主要产品以及服务有以下几个板块: ...

  9. Linux 错误: $'\r': command not found --九五小庞

    前段时间写脚本出现了$'\r': command not found问题 其实log报错已经非常明确了,是linux无法解析$'\r'.这其实是windows与linux系统的差异导致的. 因为lin ...

  10. 在SqlSugar的开发框架的Vue3+ElementPlus前端中增加对报表模块的封装处理,实现常规报表的快速处理

    在我们开发业务系统的时候,往往都需要一些数据报表进行统计查看,本篇内容介绍如何在实际的前端中对报表内容进行的一些封装操作,以便提高报表模块开发的效率,报表模块的展示主要是结合Vue3中比较广泛使用的e ...