一:背景
1. 讲故事
上个月有位敌人加微信求助,说他的程序跑着跑着就内存爆掉了,寻求如何解决,截图如下:
从聊天内容看,这位敌人压力还是蛮大的,话说这貌似是我剖析的第三个 MES 零碎了,看样子 .NET 在传统工厂是巨无霸的存在哈。。。
话不多说,一起用 Windbg 一探到底吧。
二:Windbg 剖析
1. 托管还是非托管
先看下过程的commit内存,用 !address -summary
即可。
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...--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotalMEM_PRIVATE 971 e7d6b000 ( 3.622 GB) 95.24% 90.56%MEM_IMAGE 1175 ac5d000 ( 172.363 MB) 4.43% 4.21%MEM_MAPPED 34 d08000 ( 13.031 MB) 0.33% 0.32%--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotalMEM_COMMIT 1806 edfd9000 ( 3.719 GB) 97.77% 92.97%MEM_FREE 190 c920000 ( 201.125 MB) 4.91%MEM_RESERVE 374 56f7000 ( 86.965 MB) 2.23% 2.12%...
能够看到,以后占用内存是 3.79G
,从内存地址看是一个 32bit 程序,看样子程序在解体的边缘哈,接下来咱们看下 托管堆内存
占用,应用 !eeheap -gc
命令。
0:000> !eeheap -gcNumber of GC Heaps: 1generation 0 starts at 0xf35a90c0generation 1 starts at 0xf33a1000generation 2 starts at 0x01db1000ephemeral segment allocation context: none segment begin allocated size ... f7790000 f7791000 f8058854 0x8c7854(9205844)f33a0000 f33a1000 f3ba6e84 0x805e84(8412804)Large object heap starts at 0x02db1000 segment begin allocated size02db0000 02db1000 0387e988 0xacd988(11327880)Total Size: Size: 0xdcab5ca8 (3702217896) bytes.------------------------------GC Heap Size: Size: 0xdcab5ca8 (3702217896) bytes.
从输入信息看,托管堆内存占用 3.7G
,这是一个绝对简略的 托管内存透露
问题了。
2. 探索托管堆
要查看托管堆还是很简略的,先来一个大一统的命令 !dumpheap -stat
。
0:000> !dumpheap -statStatistics: MT Count TotalSize Class Name...04b045d0 67663 25711940 xxx.Product.Mes.DataStore.EF.MesDbContext719f0100 3458387 41500644 System.Object719f1b84 281492 42391384 System.Int32[]0489adb0 2238394 44767880 xxx.Application.Features.FeatureChecker71551e00 2238503 53724072 System.Collections.Generic.List`1[[System.String, mscorlib]]07c473e0 5615923 67391076 System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlanFactory07c68954 5683589 68203068 System.Data.Entity.Core.Common.Internal.Materialization.Translator04c7e3a8 4042677 71990132 Castle.DynamicProxy.IInterceptor[]014a80c0 3142755 80480594 Free042ecd18 5869494 93911904 xxxx.Domain.Uow.UnitOfWorkInterceptor096ed32c 67663 97164068 System.Collections.Generic.Dictionary`2+Entry[[System.Type, mscorlib],[System.Data.Entity.Internal.Linq.IInternalSetAdapter, EntityFramework]][]0488edb0 12641117 151693404 xxx.Domain.Uow.AsyncLocalCurrentUnitOfWorkProvider0488fa50 10769173 215383460 xxx.Domain.Uow.UnitOfWorkManager07cc0fb0 5548261 355088704 System.Data.Entity.Core.Objects.EntitySqlQueryState719efd60 11275964 1268805768 System.String
从卦象上看,沉底的根本都是和 EF
相干的类,相对来说 string 个别都是被这些 EF 所持有,而且还发现了一个十分异样的中央,就是 MesDbContext
竟然有 6w 多,看样子有些不失常,接下来就抽几个查一下援用,大略都是如下输入:
0:000> !gcroot 17d2e438HandleTable: 014313c8 (pinned handle) -> 02dd9020 System.Object[] -> 0260abf4 System.Collections.Concurrent.ConcurrentDictionary`2[[System.Data.Entity.DbContext, EntityFramework],[System.Collections.Concurrent.ConcurrentDictionary`2[[System.String, mscorlib],[EntityFramework.DynamicFilters.DynamicFilterParameters, EntityFramework.DynamicFilters]], mscorlib]] -> b96074a4 System.Collections.Concurrent.ConcurrentDictionary`2+Tables[[System.Data.Entity.DbContext, EntityFramework],[System.Collections.Concurrent.ConcurrentDictionary`2[[System.String, mscorlib],[EntityFramework.DynamicFilters.DynamicFilterParameters, EntityFramework.DynamicFilters]], mscorlib]] -> 02fcddb0 System.Collections.Concurrent.ConcurrentDictionary`2+Node[[System.Data.Entity.DbContext, EntityFramework],[System.Collections.Concurrent.ConcurrentDictionary`2[[System.String, mscorlib],[EntityFramework.DynamicFilters.DynamicFilterParameters, EntityFramework.DynamicFilters]], mscorlib]][] -> b955eecc System.Collections.Concurrent.ConcurrentDictionary`2+Node[[System.Data.Entity.DbContext, EntityFramework],[System.Collections.Concurrent.ConcurrentDictionary`2[[System.String, mscorlib],[EntityFramework.DynamicFilters.DynamicFilterParameters, EntityFramework.DynamicFilters]], mscorlib]] -> 17d2e438 xxx.DataStore.EF.MesDbContext
从援用链来看,这些 MesDbContext
都是被 ConcurrentDictionary<DbContext,ConcurrentDictionary<string,DynamicFilterParameters>>
所持有,接下来须要判断下这个字典的 size 到底有多大,能够用 !objsize
命令。
0:000> !objsize 0260abf4e06d7363 Exception in c:\mysymbols\SOS_x86_x86_4.7.3701.00.dll\5F4FF1AE6f0000\SOS_x86_x86_4.7.3701.00.dll.objsize debugger extension. PC: 757ea842 VA: 022ce8f4 R/W: 19930520 Parameter: 7b9bb5280:000> !DumpObj /d 02fcddb0Name: System.Collections.Concurrent.ConcurrentDictionary`2+Node[[System.Data.Entity.DbContext, EntityFramework],[System.Collections.Concurrent.ConcurrentDictionary`2[[System.String, mscorlib],[EntityFramework.DynamicFilters.DynamicFilterParameters, EntityFramework.DynamicFilters]], mscorlib]][]MethodTable: 0973cb60EEClass: 715c4fc0Size: 573440(0x8c000) bytesArray: Rank 1, Number of elements 143357, Type CLASS (Print Array)Fields:None
通过漫长的期待,害,最初报错了,但也能够看到这个 dictionary 有 14.3w 条记录, 接下来严厉的问题就来了,这个 ConcurrentDictionary
是敌人定义的还是框架内的?所以下一步就须要找到它的归属类?
3. 探索字典到底属于哪个类
要想找到 字典
的归属类,这个绝对有点麻烦,我为此在 B 站上录了一集专门聊这个,有趣味的敌人能够看一看。
https://b23.tv/Rq47Vxp
总而言之,整体思路是:
- 先找 17d2e438(MesDbContext) 在 0260abf4(dictionary) 中的 address (address1) 。
- 再从内存中寻找这个 address(address1) 的 address (address2)。
这个 address2 就存在于那个援用此dictionary的办法体,而后就能够反编译出该办法体,查看它的EEClass,最终找到所属类名。
接下来咱们就实战一下。
- 查看 object[] 的 size。
0:000> !do 02dd9020Name: System.Object[]MethodTable: 719f0154EEClass: 715c4fc0Size: 65532(0xfffc) bytesArray: Rank 1, Number of elements 16380, Type CLASS (Print Array)Fields:None
- 寻找 address1
用 s -d
搜寻内存。
0:000> s -d 02dd9020 L?0xfffc 0260abf402de11a4 0260abf4 0260ad04 0260ad2c 08320d20 ..`...`.,.`. .2.
这个 02de11a4
就是我要找的 address1,这里略微解释一下,-d
示意按 32bit 搜寻, -q
按 64bit 搜寻, L?0xfffc
是 object[] 数组的 size
。
- 寻找 address2
这里将地址拆成 02de11a4 = a4 11 de 02
去搜寻,不然有坑的哈。
0:000> s-b 0 L?0xffffffff a4 11 de 020695d2f9 a4 11 de 02 e8 be 14 f9-6b b9 18 3c 34 70 e8 bc ........k..<4p..09e9438b a4 11 de 02 39 09 e8 9a-11 af 67 8b f0 a1 bc 11 ....9.....g.....
从输入看,有两个代码区域用到了 dict
, 因为是全内存搜寻的,这里就筛选最初一个 address2=09e9438b
吧。
- 反编译address2
应用 !U
反编译,而后再 !name2ee + !dumpmd + !dumpclass
即可。
0:000> !U 09e9438bNormal JIT generated codeEntityFramework.DynamicFilters.DynamicFilterExtensions.GetOrCreateScopedFilterParameters(System.Data.Entity.DbContext, System.String)Begin 09e94320, size 1e109e94320 55 push ebp...09e9433a 8bf1 mov esi,ecx09e9433c b95088ea09 mov ecx,9EA8850h (MT: EntityFramework.DynamicFilters.DynamicFilterExtensions+<>c__DisplayClass71_0)09e94341 e882ed5af7 call 014430c8 (JitHelp: CORINFO_HELP_NEWSFAST)09e94346 8bf8 mov edi,eax09e94348 8d5704 lea edx,[edi+4]09e9434b e800a5a568 call clr!JIT_WriteBarrierESI (728ee850)0:000> !name2ee *!EntityFramework.DynamicFilters.DynamicFilterExtensions.GetOrCreateScopedFilterParametersModule: 0973aef4Assembly: EntityFramework.DynamicFilters.dllToken: 0600005eMethodDesc: 0973b8fcName: EntityFramework.DynamicFilters.DynamicFilterExtensions.GetOrCreateScopedFilterParameters(System.Data.Entity.DbContext, System.String)JITTED Code Address: 09e943200:000> !dumpmd 0973b8fcMethod Name: EntityFramework.DynamicFilters.DynamicFilterExtensions.GetOrCreateScopedFilterParameters(System.Data.Entity.DbContext, System.String)Class: 0974c7d8MethodTable: 0973b938mdToken: 0600005eModule: 0973aef4IsJitted: yesCodeAddr: 09e94320Transparency: Critical0:000> !dumpclass 0974c7d8Class Name: EntityFramework.DynamicFilters.DynamicFilterExtensionsmdToken: 02000006File: D:\xxx\Debug\EntityFramework.DynamicFilters.dllParent Class: 715415b0Module: 0973aef4Method Table: 0973b938Vtable Slots: 4Total Method Slots: 20Class Attributes: 100181 Abstract, Transparency: CriticalNumInstanceFields: 0NumStaticFields: 5 MT Field Offset Type VT Attr Value Name0973bfcc 400000d c ....DynamicFilters]] 0 static 0260a9d4 _GlobalParameterValues0973c3f4 400000e 10 ...ers]], mscorlib]] 0 static 0260abf4 _ScopedParameterValues70343c18 400000f 14 ...tring, mscorlib]] 0 static 0260ad04 _PreventDisabledFilterConditions71a34804 4000010 43 System.Boolean 1 static 1 _Initialized05ec9adc 4000011 18 ...rsion, mscorlib]] 0 static 0260ad2c _OracleInstanceVersions
终于给找到了,原来是EF底层的 EntityFramework.DynamicFilters.DynamicFilterExtensions
类哈,导出源码如下:
最初就是拿 6w多的 MesDbContext
和 14w+的 _ScopedParameterValues
字典和敌人做了沟通,敌人也找到了解决办法。
三:总结
依据敌人提供的信息,最初正文掉了构造函数中的 MesDbContext
解决了问题,EF我不熟,有懂的敌人能够留言剖析下哈。