一:背景

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

总而言之,整体思路是:

  1. 先找 17d2e438(MesDbContext) 在 0260abf4(dictionary) 中的 address (address1) 。
  2. 再从内存中寻找这个 address(address1) 的 address (address2)。

这个 address2 就存在于那个援用此dictionary的办法体,而后就能够反编译出该办法体,查看它的EEClass,最终找到所属类名。

接下来咱们就实战一下。

  1. 查看 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
  1. 寻找 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

  1. 寻找 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 吧。

  1. 反编译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我不熟,有懂的敌人能够留言剖析下哈。