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

38

我们的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报告中的全局视角看问题。

  1. 进程正在进行垃圾回收,所以根据警告,我不应该信任!heap命令的所有输出。

  2. Gc堆:1.37 G字节

  3. .NET缓存大小为750Mb,

    虚拟内存详细信息:
    虚拟分配:17.45 Gb
    已加载模块:208.68 Mb
    线程:25 Mb
    本机堆:3.06 Gb(我对此感到担忧。)

以上3.02 Gb仅存在于Heap 0x003f0000上。
我们有大量的流量,因此1.3 gb的Gc堆大小对我来说感觉很正常。此外,我们拥有32 gb内存和64位地址空间的机器,因此750 mb的缓存大小是可以接受的。根据本机堆的大小,我认为这是本机内存泄漏。
DebugDiag警告:转储文件中有18149个动态程序集加载。

帮助链接:
.NET内存泄漏:通过XmlSerializing方式导致内存泄漏
分析-我们确实使用XmlSerialisers,但它们被缓存,这样它们只会被创建一次。

.NET内存泄漏:XslCompiledTransform和泄漏的动态程序集
我们似乎有与此博客文章描述的相同问题。所有这些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 )

Finalizer队列中的顶级对象

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

应用程序域统计信息
Domain - Default - 13个程序集 - 大小:89,677,824(90 Mb〜) Domain - ROOT/tv/Engine1 - 18236个程序集 - 大小: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 Finalizer线程,但并非如此。它正在等待终结事件的发生。

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
Dump中泄漏的动态程序集数量与 System.Reflection.Emit.InternalModuleBuilderSystem.Reflection.Emit.InternalAssemblyBuilder 对象数量相同。我注意到 Top finalizer 队列中有 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不太熟悉,有人可以给我一些提示吗?

  1. 如何将上述动态模块与错误代码联系起来。我认为这是由于Linq或Lambda表达式引起的。
  2. 根据报告,动态程序集的大小为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

检查一下你的第三方DLL,看看是否有任何动态创建对象的情况。我曾经在错误使用Castle Dynamix代理对象时遇到过类似的内存泄漏问题。 - jurgenb
请检查您的代码是否正确释放所有内容,捕获所有异常(甚至在使用过程中),并释放所有引用。请附上使用这些程序集的代码。 - Dexion
"Assemblies" 不一定是 DLL,因此它们不会被 lm 列出。 - Thomas Weller
3
据我所知,程序集只能随着应用程序域一起被卸载,它们不会被垃圾回收。 - Thomas Weller
如果你能使用Ants内存分析工具,它会对您有很大帮助。 - guiomie
显示剩余11条评论
5个回答

3
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。
我见过这两种泄漏情况的发生,并且迅速消耗了相当大规模服务器上的所有内存,因此我希望我的经验为您提供答案,或者至少为跟踪问题提供一些额外的提示。

0

为了诊断哪些程序占用了大量的内存,您可以使用

!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

如果你的例子是第三代,你需要查看它所使用的数据结构。连续的 85k+ 通常使用的是 byte[] 或者 string

0
我们已经看到了在代码中使用非线程安全集合时出现的问题。由于大多数人喜欢将数据读入集合中,这似乎是一个与线程相关的“重复”问题。
在此处查看线程安全集合列表: Thread Safe Collections 还有一件事要补充。我们使用RedGate/ANTS分析器。同时,请检查您的连接管理和WCF服务代码的清理工作。

0

0

仅仅是一个大胆的建议,我可以告诉你,缓冲(默认)的WCF服务会消耗很多额外的内存。如果你切换到流式传输,你可以获得巨大的内存节省。

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

在一个案例中,我的WCF服务通过切换到缓冲方式减少了512兆的内存使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接