一:背景

1. 讲故事

中秋国庆长假结束,哈哈,在老家拍了很多的短视频,有兴趣的可以上B站观看:https://space.bilibili.com/409524162 ,今天继续给大家分享各种奇奇怪怪的.NET生产事故,希望能帮助大家在未来的编程之路上少踩坑。

话不多说,这篇看一个.NET程序集泄露导致的CLR私有堆泄露的案例,这个泄露和 JsonConvert 有关,哈哈,相信你肯定比较惊讶!

二:WinDbg 分析

1. 到底是哪里的泄露

首先观察一下进程的提交内存的大小,即通过 !address -summary 观察。


0:000> !address -summary
--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free 390 7dfa`63fa8000 ( 125.978 TB) 98.42%
<unknown> 13628 205`32974000 ( 2.020 TB) 99.92% 1.58%
Heap 8143 0`4042b000 ( 1.004 GB) 0.05% 0.00%
Stack 186 0`1f8e0000 ( 504.875 MB) 0.02% 0.00%
Image 1958 0`09775000 ( 151.457 MB) 0.01% 0.00%
Other 9 0`001d7000 ( 1.840 MB) 0.00% 0.00%
TEB 62 0`0007c000 ( 496.000 kB) 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_MAPPED 312 200`00a06000 ( 2.000 TB) 98.92% 1.56%
MEM_PRIVATE 21717 5`91ecd000 ( 22.280 GB) 1.08% 0.02%
MEM_IMAGE 1958 0`09775000 ( 151.457 MB) 0.01% 0.00% --- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE 390 7dfa`63fa8000 ( 125.978 TB) 98.42%
MEM_RESERVE 4509 205`0fc14000 ( 2.020 TB) 99.89% 1.58%
MEM_COMMIT 19478 0`8c434000 ( 2.192 GB) 0.11% 0.00%

当前的提交内存占用了 2.19G,进程堆占用 1G ,差不多占了一半,但不能说明就是非托管内存泄露,接下来继续观察下托管堆。


0:000> !eeheap -gc
Number of GC Heaps: 8
------------------------------
Heap 7 (000001C4971013A0)
generation 0 starts at 0x000001C817D201A0
generation 1 starts at 0x000001C817C878D8
generation 2 starts at 0x000001C817261000
ephemeral segment allocation context: none
segment begin allocated size
000001C817260000 000001C817261000 000001C819013F98 0x1db2f98(31141784)
Large object heap starts at 0x000001C907261000
segment begin allocated size
000001C907260000 000001C907261000 000001C907261018 0x18(24)
Pinned object heap starts at 0x000001C987261000
000001C987260000 000001C987261000 000001C9872ABA50 0x4aa50(305744)
Heap Size: Size: 0x1dfda00 (31447552) bytes.
------------------------------
GC Heap Size: Size: 0xba26488 (195191944) bytes.

从卦中可以看到当前的托管堆占用仅 195M,这就更好的验证当前确实存在非托管内存泄露,由于非托管内存没有开启 ust,也没有 perfview 的etw文件,所以没有好的方式进一步挖掘,到这里可能就止步不前了。

2. 到底是哪里的泄露

在 C# 所处的 Windows 进程中,其实有很多的堆,比如:crt堆,ntheap堆,gc堆,clr私有堆,堆外(VirtualAlloc),调试没有标准答案,不断的假设,试探,摸着石头过河,言外之意就是这个堆没问题,不代表其他堆也没有问题,这样想思路就比较顺畅了,我们可以看看其他的堆,比如这里的 CLR私有堆,使用 !eeheap -loader 观察。


0:000> !eeheap -loader
Loader Heap:
--------------------------------------
...
Module 00007ff846e034c0: Size: 0x0 (0) bytes.
Module 00007ff846e03930: Size: 0x0 (0) bytes.
Module 00007ff846e04180: Size: 0x0 (0) bytes.
Module 00007ff846e047e0: Size: 0x0 (0) bytes.
Module 00007ff846e04e40: Size: 0x0 (0) bytes.
Total size: Size: 0x0 (0) bytes.
--------------------------------------
Total LoaderHeap size: Size: 0x47252000 (1193615360) bytes total, 0x1f68000 (32931840) bytes wasted.
=======================================

从卦中可以看到有非常多的 module 迸射出来,估计有几万个,并且可以看到总的大小是 1.19G,到这里基本就搞清楚了,然来是 程序集泄露

这里稍微补充一下,像这种问题早期可以使用 dotnet-counter 或者 Windows 的程序集指标 监控一下,或许你就能轻松找出原因,截图如下:


PS C:\Users\Administrator\Desktop> dotnet-counters monitor -n WebApplication2

而且 dotnet-counter 还是跨平台的,非常实用,大家可以琢磨琢磨,接下来抽一个module 用命令 !dumpmodule -mt 00007ff846e034c0 观察下,内部到底有哪些类型。


0:000> !dumpmodule -mt 00007ff846e034c0
Name: Unknown Module
Attributes: Reflection IsDynamic IsInMemory
Assembly: 000001c9e193b9e0
BaseAddress: 0000000000000000
... Types defined in this module MT TypeDef Name
------------------------------------------------------------------------------
00007ff846e03db0 0x02000002 Types referenced in this module MT TypeRef Name
------------------------------------------------------------------------------
00007ff820ff5748 0x02000002 xxx.xxx.Json.Converters.PolymorphismConverter`1
00007ff820e710f8 0x02000003 xxx.xxx.Models.IApiResult 0:000> !dumpmt -md 00007ff846e03db0
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
00007FF822F05FA8 00007ff823285b50 NONE xxx.Json.Converters.PolymorphismConverter`1
00007FF822EFD5E8 00007ff82323b1b8 NONE System.Text.Json.Serialization.JsonConverter`1
00007FF822EFD5F0 00007ff82323b1c8 NONE System.Text.Json.Serialization.JsonConverter`1
00007FF8414CB978 00007ff846e03d88 JIT IApiResultDynamicJsonConverter..ctor()

仔细分析卦中信息,可以很明显的看到。

  • Json.Converters.PolymorphismConverter

看样子和牛顿有关系,并且还是一个自定义的 JsonConvert。

  • IApiResult 和 IApiResultDynamicJsonConverter

看样子是一个接口的返回协议类,需要在代码中重点关注。

有了这些信息,接下来就是重点关注代码中的 PolymorphismConverter 类,果然就找到了一处。

从类的定义来看,一般这种东西都是在 ConfigureServices 方法中做 初始化定义 的,按理说问题不大,那为什么会有问题呢?还得要查下它的引用,终于给找到了,截图如下:

这是一个低级错误哈,每次读取 ApiResult.Data 的时候都要 jsonSerializerOptions.AddPolymorphism(); 操作,也就每次都会创建程序集,终于真相大白。

三:总结

这种程序集泄露导致的生产事故不应该哈,反应了团队中多人协作的时候还是有待提高!

记一次 .NET某账本软件 非托管泄露分析的更多相关文章

  1. 记一次 .NET 某工控视觉软件 非托管泄漏分析

    一:背景 1.讲故事 最近分享了好几篇关于 非托管内存泄漏 的文章,有时候就是这么神奇,来求助的都是这类型的dump,一饮一啄,莫非前定.让我被迫加深对 NT堆, 页堆 的理解,这一篇就给大家再带来一 ...

  2. 记一次 .NET 某打印服务 非托管内存泄漏分析

    一:背景 1. 讲故事 前段时间有位朋友在微信上找到我,说他的程序出现了内存泄漏,能不能帮他看一下,这个问题还是比较经典的,加上好久没上非托管方面的东西了,这篇就和大家分享一下,话不多说,上 WinD ...

  3. 记一次 .NET 某手术室行为信息系统 内存泄露分析

    一:背景 1. 讲故事 昨天有位朋友找到我,说他的程序内存存在泄露导致系统特别卡,大地址也开了,让我帮忙看一下怎么回事?今天上午看了下dump,感觉挺有意思,在我的分析之旅中此类问题也蛮少见,算是完善 ...

  4. 记一次 .NET 某智慧水厂API 非托管内存泄漏分析

    一:背景 1. 讲故事 七月底的时候有位朋友在wx上找到我,说他的程序内存占用8G,托管才占用1.5G,询问剩下的内存哪里去了?截图如下: 从求助内容看,这位朋友真的太客气了,动不动就谈钱,真伤感情, ...

  5. 记一次 .NET 某桌面奇侠游戏 非托管内存泄漏分析

    一:背景 1. 讲故事 说实话,这篇dump我本来是不准备上一篇文章来解读的,但它有两点深深的感动了我. 无数次的听说用 Unity 可做游戏开发,但百闻不如一见. 游戏中有很多金庸武侠小说才有的名字 ...

  6. 记一次 .NET 某电子厂OA系统 非托管内存泄露分析

    一:背景 1.讲故事 这周有个朋友找到我,说他的程序出现了内存缓慢增长,没有回头的趋势,让我帮忙看下到底怎么回事,据朋友说这个问题已经困扰他快一周了,还是没能找到最终的问题,看样子这个问题比较刁钻,不 ...

  7. 记一次 .NET 某工控软件 内存泄露分析

    一:背景 1.讲故事 上个月 .NET调试训练营 里的一位老朋友给我发了一个 8G 的dump文件,说他的程序内存泄露了,一时也没找出来是哪里的问题,让我帮忙看下到底是怎么回事,毕竟有了一些调试功底也 ...

  8. C#的三大难点之二:托管与非托管

    相关文章: C#的三大难点之前传:什么时候应该使用C#?​C#的三大难点之一:byte与char,string与StringBuilderC#的三大难点之二:托管与非托管C#的三大难点之三:消息与事件 ...

  9. 关于Dll、Com组件、托管dll和非托管dll

    转自:https://blog.csdn.net/black_bad1993/article/details/53906252 Com组件 1.线程模型是干嘛用的?解决"多个线程" ...

  10. debug实战:Unmanaged High Memory非托管高内存

    最近又监控到一个高内存的问题,周五下班把系统打开,周末2天没关,周一来看已经涨到5.2G,这次与以往不同,不是.net的内存泄漏,而是非托管引起的. 1. 抓dump,确定高内存的类型 //dump有 ...

随机推荐

  1. Flutter状态管理新的实践

    1 背景介绍 1.1 声明式ui 声明式UI其实并不是近几年的新技术,但是近几年声明式UI框架非常的火热.单说移动端,跨平台方案有:RN.Flutter.iOS原生有:SwiftUI.android原 ...

  2. Java 使用ArrayList获取10个1-20之间的随机数,要求不能重复

    代码如下: public static void main(String[] args) { List<Integer> nums = new ArrayList<Integer&g ...

  3. Unity的Undo:详解解析与实用案例

    Unity Undo详解 在Unity中,Undo是一个非常重要的功能,它可以让开发者在编辑器中进行操作时,随时撤销之前的操作,从而避免不必要的错误.本文将详细介绍Unity Undo实现原理和使用方 ...

  4. AntdPro中formItemProps和fieldProps的区别

    最近在工作中接触到了 antd 和 antd pro,作为一个 react 和 antd 新人,在学习和使用中遇到了不少的问题,下边就常见的一个问题来进行记录,后续还会记录更多的问题以及心得 Form ...

  5. 带你掌握利用Terraform不同数据源扩展应用场景

    本文分享自华为云社区<利用Terraform不同数据源扩展应用场景>,作者: kaliarch . 一 背景 在生产环境中使用Terraform进行基础设施编排,通常又一些信息是通过其他外 ...

  6. 路由分发admin源码

    1. 不使用include方法 1.表象 另一种路由编写方式,不用使用include 引出namespace概念 可以看到如果使用include方法,后面可带上一个参数赋值给namespace 在无n ...

  7. 利用Anaconda3安装tensorflow/keras,并迁移虚拟环境至不能上网的电脑

    利用Anaconda3安装tensorflow/keras,并迁移虚拟环境至不能上网的电脑 下面记录下利用Anaconda安装tensorflow和keras,前前后后也踩了不少坑.并分别在windo ...

  8. Linux 命令:find/grep/sed/awk/du/df

    目录 find grep sed awk du/df find # find 属于全部匹配,如输入abc,不能查到abcd # find 默认采用递归搜索 # 按时间 find . -atime -1 ...

  9. 2021-11-18 wpf模板

    自定义模板 <ControlTemplate x:Key="ButtonStyle1" TargetType="Button"> <Borde ...

  10. 从javascript代码解析过程理解执行上下文与作用域提升

    javascript代码解析过程 执行上下文和作用域是javascript中非常重要的部分,要弄清楚它们首先就要说到javascript的运行机制,javascript代码被解析经过了以下几个步骤 P ...