.NET WCF w3wp 本机内存泄漏和加载程序堆中 0 大小的 18k 动态程序集

Posted

技术标签:

【中文标题】.NET WCF w3wp 本机内存泄漏和加载程序堆中 0 大小的 18k 动态程序集【英文标题】:.NET WCF w3wp native memory leak and 18k dynamic assemblies of 0 sizes in loader heap 【发布时间】:2015-04-19 07:33:54 【问题描述】:

我们的 WCF 服务显示一个使用大量内存的实例,因此我们进行了完整的内存转储来确定问题。

Operating System   Windows Server 2008 R2Service Pack 1 
Number Of Processors   4 
Process Image   c:\Windows\System32\inetsrv\w3wp.exe 
System Up-Time   40 day(s) 09:23:09 
Process Up-Time   14 day(s) 11:49:01 
.NET 4.0
Processor Type   X64 
Process Bitness   64-Bit

DebugDiag 报告中问题的直升机视图。

    进程正在收集垃圾,因此根据警告,我不应该相信 !heap 命令的所有输出。

    Gc 堆:1.37 GB

    .NET 缓存大小为 750Mb,

    虚拟内存详细信息: 虚拟分配:17.45 Gb 加载模块:208.68 Mb 线程:25 Mb 本机堆:3.06 Gb(我很担心。)

从上方看,3.02 Gb 仅存在于 Heap 0x003f0000 上。 我们有大量的流量,这样1.3 gb Gc 堆大小对我来说感觉很正常。另外我们的机器有32 gb ram 和64 位地址空间,所以750 mb 的缓存大小是可以接受的。根据 Native heap 的大小,我感觉这是原生内存泄漏。

DebugDiag 警告: 转储文件中加载了 18149 个动态程序集。

帮助链接:.NET Memory Leak: XmlSerializing your way to a Memory Leak 分析 - 我们确实使用了 XmlSerialisers,但它们被缓存了,因此它们只被创建一次。

.NET Memory Leak: XslCompiledTransform and leaked dynamic assemblies 我们似乎在这篇博文中描述了同样的问题。所有这 18149 个动态程序集的大小均为 0。所以我不能转储它们以获取详细信息。此外,我们不会在此应用程序的任何地方使用 Xsl 转换。所以这些程序集不是由于 Xsl 转换造成的。

更多统计数据:相关对象数:

System.Reflection.Emit.InternalModuleBuilder -----   1.11 MBytes    (18149 objects )
System.Reflection.Emit.InternalAssemblyBuilder ----- 992.52 KBytes    (18149 objects ) 
System.Reflection.Emit.__FixupData[]  ----------   595.41 KBytes    (752 objects )
System.Reflection.Emit.GenericFieldInfo  ----------  580.03 KBytes    (18561 objects )
System.Reflection.RuntimeMethodInfo  ----------   1.2 MBytes    (11276 objects )
System.RuntimeType --------------------     1.13 MBytes    (21228 objects )

终结器队列中的***对象

System.Reflection.Emit.DynamicResolver - 379 System.Reflection.Emit.DynamicResolver+DestroyScout - 271

应用领域统计Domain - Default - 13 assemblies - size : 89,677,824 (90 Mb ~) Domain - ROOT/tv/Engine1 - 18236 Assemblies- Size :152,834,048 (150 Mb ~)

我猜这些泄露的动态程序集占用了 150 Mb 的空间。不确定 3 Gb 的本机内存是否归因于这些程序集?

更多关于这个程序集的挖掘:

!dumpdomain 给我大型未知动态程序集,如下所示:

Assembly:           000000000fa9d0d0 (Dynamic) []
ClassLoader:        0000000002be1d40
SecurityDescriptor: 000000000fc08a00
  Module Name
000007fe96d38e68            Dynamic Module

and  !EEHeap -loader gives same number of 0 sized modules : 
Module 000007fea0b7b758: Size: 0x0 (0) bytes.
Module 000007fea0b7c1e8: Size: 0x0 (0) bytes.
Module 000007fea0b7cc78: Size: 0x0 (0) bytes.

从堆栈跟踪下方检查阻塞的 GC 终结器线程并非如此。它正在等待完成事件发生。

0:000> ~20 k
Child-SP          RetAddr           Call Site
00000000`0eedf3b8 000007fe`fd6f1430 ntdll!ZwWaitForMultipleObjects+0xa
00000000`0eedf3c0 00000000`77501723 KERNELBASE!WaitForMultipleObjectsEx+0xe8
00000000`0eedf4c0 000007fe`f60939d4 kernel32!WaitForMultipleObjectsExImplementation+0xb3
00000000`0eedf550 000007fe`f6094799 clr!SVR::WaitForFinalizerEvent+0xcc
00000000`0eedf590 000007fe`f5f0458c clr!SVR::GCHeap::FinalizerThreadWorker+0x4a
00000000`0eedf5d0 000007fe`f5f0451a clr!Frame::Pop+0x50

转储具有与泄漏的动态程序集相同数量的System.Reflection.Emit.InternalModuleBuilderSystem.Reflection.Emit.InternalAssemblyBuilder 对象。

我注意到顶部终结器队列中的System.Reflection.Emit.DynamicResolver 并转储了所有它们并将它们与动态程序集地址相关联,如下所示。 转储了大约 5 个 DynamicResolver 对象并跟踪 DynamicResolver -> m_method -> m_module (00000001801728a0)

00000001801728a0 这是 InternalModuleBuilder 列表中一个模块的地址。他们中的大多数都指向同一个模块。

0:000> !dumpheap -type System.Reflection.Emit.DynamicResolver
         Address               MT     Size
000000018017d5a8 000007fef4c7c8b0       72     
000000018018d5b0 000007fef4c7c8b0       72     
00000001801931b0 000007fef4c7c8b0       72     
------- and on


0:000> !do 000000018017d5a8
Name:        System.Reflection.Emit.DynamicResolver
MethodTable: 000007fef4c7c8b0
EEClass:     000007fef4754300
Size:        72(0x48) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fef4c44458  4002aaa        8      System.Object[]  0 instance 0000000000000000 m_exceptions
000007fef4c9a690  4002aab       10        System.Byte[]  0 instance 0000000000000000 m_exceptionHeader
000007fef4ca20c0  4002aac       18 ...mit.DynamicMethod  0 instance 0000000180172690 m_method
000007fef4c9a690  4002aad       20        System.Byte[]  0 instance 000000018017d5f0 m_code
000007fef4c9a690  4002aae       28        System.Byte[]  0 instance 000000018017d650 m_localSignature
000007fef4c992b8  4002aaf       38         System.Int32  1 instance                3 m_stackSize
000007fef4c7c788  4002ab0       30 ...Emit.DynamicScope  0 instance 0000000180172b80 m_scope

0:000> !do 0000000180172690 
Name:        System.Reflection.Emit.DynamicMethod
MethodTable: 000007fef4ca20c0
EEClass:     000007fef475e298
Size:        112(0x70) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fef4c44458  4002ac6        8      System.Object[]  0 instance 0000000180172700 m_parameterTypes
000007fef4cafa88  4002ac7       10 ...RuntimeMethodInfo  0 instance 000000018017d678 m_methodHandle
000007fef4c987f8  4002ac8       18   System.RuntimeType  0 instance 00000004800e7900 m_returnType
000007fef4c7c578  4002ac9       20 ...ynamicILGenerator  0 instance 0000000180172a30 m_ilGenerator
000007fef4c4eb18  4002aca       28 ...mit.DynamicILInfo  0 instance 0000000000000000 m_DynamicILInfo
000007fef4c97de0  4002acb       60       System.Boolean  1 instance                1 m_fInitLocals
000007fef4c9f1d8  4002acc       30 ...ion.RuntimeModule  0 instance 00000001801728a0 m_module
000007fef4c97de0  4002acd       61       System.Boolean  1 instance                0 m_skipVisibility
000007fef4c987f8  4002ace       38   System.RuntimeType  0 instance 0000000000000000 m_typeOwner
000007fef4c7c330  4002acf       40 ...d+RTDynamicMethod  0 instance 00000001801729d8 m_dynMethod
000007fef4c7c8b0  4002ad0       48 ...t.DynamicResolver  0 instance 000000018017d5a8 m_resolver
000007fef4c97de0  4002ad1       62       System.Boolean  1 instance                0 m_profileAPICheck
000007fef4c99d18  4002ad2       50 ...n.RuntimeAssembly  0 instance 0000000000000000 m_creatorAssembly
000007fef4c97de0  4002ad3       63       System.Boolean  1 instance                1 m_restrictedSkipVisibility
000007fef4c88d70  4002ad4       58 ...g.CompressedStack  0 instance 00000001801729b0 m_creationContext
000007fef4c88020  4002ad5     16b8 ...rnalModuleBuilder  0   shared           static s_anonymouslyHostedDynamicMethodsModule
                                 >> Domain:Value  0000000002b66ba0:NotInit  0000000002c24a90:00000001801728a0 <<
000007fef4c96ae8  4002ad6     16c0        System.Object  0   shared           static s_anonymouslyHostedDynamicMethodsModuleLock
                                 >> Domain:Value  0000000002b66ba0:NotInit  0000000002c24a90:0000000180172798 <<


Opened log file 'C:\debug\new_dynamic_asm.log'
0:000> !dumpheap -type System.Reflection.Emit.InternalModuleBuilder
         Address               MT     Size
00000001800fe918 000007fef4c88020       64     
00000001801728a0 000007fef4c88020       64     
000000018017fa88 000007fef4c88020       64     
00000001801bee20 000007fef4c88020       64  
------- and on

我对 WinDbg 不是很方便,谁能给我一些线索。

    如何关联上述动态模块以获取错误代码。我相信这是由于 Linq 或 Lambda 表达式造成的。 根据报告,动态程序集的大小为 150 Mb,3 Gb 泄漏将是任何不同的或动态模块可能链接到某些本机内存。

!heap -l 给了我 检测到 188722 个潜在的不可达块。

使用 WinDbg PyKd 插件的本机堆统计数据为我提供了以下本机堆的统计信息。

注意值在 18,000 左右滚动

Statistics:

                                         Type name       Count  Size

                                    clr!RecordPool      817335  Unknown

                                       clr!RegMeta      272445  Unknown

                                 clr!CBlobPoolHash       36326  Unknown

                                  clr!MDInternalRW       36326  Unknown

                                   clr!StgBlobPool       36326  Unknown

                                       clr!CCeeGen       36298  Unknown

                                    clr!PEAssembly       18267  Unknown

                    clr!AssemblySecurityDescriptor       18249  Unknown

                                clr!DomainAssembly       18249  Unknown

                      clr!SharedSecurityDescriptor       18236  Unknown

                               clr!CStringPoolHash       18163  Unknown

                                     clr!CMiniMdRW       18163  Unknown

                                   clr!StgGuidPool       18163  Unknown

                                 clr!StgStringPool       18163  Unknown

                                 clr!CCustAttrHash       18163  Unknown

                                 clr!CGuidPoolHash       18163  Unknown

                                  clr!PESectionMan       18149  Unknown

                              clr!CeeSectionString       18149  Unknown

                                     clr!PESection       18149  Unknown

                           nativerd!CONFIG_ELEMENT        4932  Unknown

                          nativerd!ATTRIBUTE_VALUE        3912  Unknown

                         nativerd!SCHEMA_ATTRIBUTE        1473  Unknown

                                 clr!CAssemblyName        1116  Unknown

                     nativerd!COLLECTION_KEY_ENTRY         919  Unknown

                           nativerd!SCHEMA_ELEMENT         766  Unknown

                      clr!AssemblyMDInternalImport         720  Unknown

                           nativerd!CONFIG_SECTION         652  Unknown

                        nativerd!CONFIG_COLLECTION         570  Unknown

                 clr!ListNode<CHashNode * __ptr64>         444  Unknown

【问题讨论】:

您的应用程序中的某些东西似乎生成了新的程序集。你在使用 System.Reflection.Emit 吗?你能运行lmv(列出模块详细)命令吗?它应该列出所有 18k 模块以及附加信息。也许这些信息有助于理解您的应用程序为什么会生成新的程序集。 您可以尝试的另一件事是使用 DebugDiag 集合并在 mscorlib.dll!System.AppDomain.DefineDynamicAssembly 上设置断点并记录堆栈跟踪。它可以帮助您找到这些程序集的生成者。 我们的代码直接不引用 System.Reflection.Emit,不确定第三方库。命令 lmv 肯定会提供很多信息。 lm 只显示 dtagent.dll lmv 显示它是 DynaTrace Agent,这是我们的 apphosting 使用的分析软件。这可能导致此泄漏,需要确认。 我无权访问服务器以使用 debugdiag 放置断点,如果我能以某种方式完成此操作,我会尝试。谢谢 AFAIK,程序集只能与应用程序域一起卸载。它们不会被垃圾回收。 【参考方案1】:

要诊断什么正在使用大量内存,您想使用

!dumpheap -stat

这将通过汇总实例数来总结对象使用情况。囤积内存的一个区域是大对象堆(任何超过 85k 的东西)。除非绝对必要,否则不会对这个区域进行 GC。

要专门查看 LOH,您可以使用:

!dumpheap -stat -min 85000

对于上面你关心的项目,你需要找出它们在哪一代。这可以通过找到项目的地址然后查看!DumpObject的输出来完成,如下所示:

> 0:000> !do 0x0000000011b47450 
Name: System.Byte[]
MethodTable: 000007fef7d2e798
EEClass: 000007fef7932670
Size: 131096(0x20018) bytes
GC Generation: 3  <--- ****** Gen 3 == LOH
Array: Rank 1, Number of elements 131072, Type Byte
Element Type: System.Byte
Fields:
None

如果在您的示例中是第 3 代,您需要查看它是什么数据结构。 85k+ 连续通常是byte[]strings。

【讨论】:

【参考方案2】:

我们已经看到了在代码中使用线程安全集合而不是使用线程安全集合的问题。由于大多数人喜欢将数据读入集合中,因此这似乎是一个“重复”的问题。

在此处查看线​​程安全集合的列表: Thread Safe Collections

还有一点要补充。我们使用 RedGate/ANTS 分析器。还要检查您的连接管理和 WCF 服务代码的清理。

【讨论】:

【参考方案3】:

它可能是某种在编译时未知的 lambda 查询。

您可以让开发人员查看将 Microsoft Azure 的 Application Insights 添加到相关网站。然后你可以找到哪个页面上有长时间运行的代码,这样你就可以缩小受影响的代码范围。

https://azure.microsoft.com/en-us/documentation/articles/app-insights-start-monitoring-app-health-usage/

【讨论】:

【参考方案4】:

作为一个大胆的建议,我可以告诉您缓冲(默认)WCF 服务会占用大量额外内存。如果您切换到流式传输,则可以节省大量内存。

见这篇文章https://msdn.microsoft.com/en-us/library/ms731913(v=vs.110).aspx。

在一种情况下,我的 WCF 服务通过切换到缓冲的方式将内存使用量减少了 512meg。

【讨论】:

【参考方案5】:

WCF 自动在内存中为某些通信协议生成序列化类,主要用于 XML 通信,并且似乎为消息结构中的每个可能变化创建不同的类;这很容易解释组件的数量。对于基于 XML 的 WCF 协议,这种行为显然是正常的。如果您可以控制协议,切换到非 XML 通信协议可能会解决此问题。

3GB 内存消耗是合理的 - 动态程序集将存在于内存中的 MSIL(.NET 汇编语言)和本机版本中。 150MB 可能用于最初由 WCF 生成的 MSIL 版本,不包括在 MSIL 版本作为模块“加载”并使其可调用后立即由 .NET JIT 编译器生成的本机代码。

17.45GB 的虚拟空间不是真正的内存,而是加载这些模块的最低和最高内存位置之间的距离;例如,如果主模块在偏移量 0 处加载,而第一个动态程序集在 00000000:0b000000 处加载,则列出的总虚拟内存约为 185MB,即使主模块仅占用 2MB 而动态程序集又占用 2MB。这是一个夸张的例子,因为它们通常打包得很好,但是地址之间通常是 1MB,所以 18000 * 1MB = 18000MB,除以 1024 得到正好 17GB 的地址空间(为系统的其余部分再添加半 GB并且您拥有完整的虚拟地址空间)。

我还看到了另一种使用 WCF 快速增长的内存泄漏:如果消息的任何部分由应用程序的持久组件持有,则底层 XML COM 对象将不会被垃圾收集,从而导致相当大量内存泄漏。 .NET XML DOM 使用 Windows COM XML 子系统,它是本机代码,并在本机堆上分配。这可以解释托管内存堆和本机内存堆之间的区别。查看转储中的实际内存(寻找可以过滤掉可打印文本的工具),并检查其中有多少是 XML 格式的。

我已经看到这两种泄漏都发生了,并且迅速消耗了相当大型服务器上的所有内存,所以我希望我的经验可以为您提供答案,或者至少为您提供一些额外的提示来追踪您的问题.

【讨论】:

以上是关于.NET WCF w3wp 本机内存泄漏和加载程序堆中 0 大小的 18k 动态程序集的主要内容,如果未能解决你的问题,请参考以下文章

IIS 网站上托管的 WCF 服务 TransferMode = Streamed 引发 w3wp.exe OutOfMemory 异常?

本机 C 和 Fortran 代码的 Java 内存泄漏

如何诊断由 IIS7 托管的 WCF 服务中的 W3WP.exe 错误

服务器上 WCF(双工)中的内存泄漏

如何使用 Malloc Debug 来检查本机内存泄漏?

c++ 内存泄漏问题