首先什么是Out Of Memory?就是内存溢出简称OOM(下边我就用这个简称了啊!)说白了就是程序想用内存的时候OS没有那么多内存可以分配了然后就抱OOM错误了 首先介绍一下我这个项目的情况基于exchange +sp+hmc+web service call通过一个winform的模拟测试程序单线程添加信息循环万次每循环一次创建一个公司开通邮件域名并创建个帐号每个帐号都开通邮件服务现在循环到次左右的时候wwpexe的内存占用为private bytesMvirtual bytesM听兄弟讲他们做过类似的测试当循环到个的时候会出现OOM的问题 既然是OOM我们当然要介绍一个超级cool的工具debugdiag!(这个工具以后再介绍因为要贴N多图实在痛苦……)通过debugdiag抓memory leak的dump(M)发现有如下问题mscorwks洩漏了M左右的内存 现在转到windbg中来我们首先看命令!eeheap它一共有两个参数 > !help eeheap
!EEHeap [gc] [loader] 首先看一下gc的参数!eeheap gc这个命令表明我们程序占用托管堆的大小 > !eeheap gc Number of GC Heaps generation starts at xfff generation starts at xfdfc generation starts at xc ephemeral segment allocation context (xbcbf xbdc) segment begin allocated size ead acc ad xedc() ea da fb xfb() c c bdc xfcc() Large object heap starts at xc segment begin allocated size c c cfbbd xabd() Total Size xad()
GC Heap Size xad() dump大小为M托管堆大小为M不到差别很大的!剩下的内存在哪里?现在来看一个新的命令!address运行后会有一坨又一坨的输出我们关心的是后面的summary如下 Usage SUMMARY TotSize ( KB) Pct(Tots) Pct(Busy) Usage b ( ) % % RegionUsageIsVAD cb ( ) % % RegionUsageFree d ( ) % % RegionUsageImage fc ( ) % % RegionUsageStack ( ) % % RegionUsageTeb ( ) % % RegionUsageHeap ( ) % % RegionUsagePageHeap ( ) % % RegionUsagePeb ( ) % % RegionUsageProcessParametrs ( ) % % RegionUsageEnvironmentBlock Tot fff ( KB) Busy bc ( KB) Type SUMMARY TotSize ( KB) Pct(Tots) Usage cb ( ) % <free> c ( ) % MEM_IMAGE a ( ) % MEM_MAPPED ad ( ) % MEM_PRIVATE State SUMMARY TotSize ( KB) Pct(Tots) Usage ee ( ) % MEM_COMMIT cb ( ) % MEM_FREE ca ( ) % MEM_RESERVE 上面的信息比较有意思RegionUsageImage代表的是dlls占用的内存一共是MRegionUsageheap代表的是NT heaps一共是MMEM_COMMIT和MEM_RESERVE加起来是virtual memory他俩的合计是M我们还少看了什么?!eeheap还有一个参数是loader运行一下后会有N长的结果我们看一部分 !eeheap –loader Domain fbd LowFrequencyHeap ba() d() ee() b() ea() f(f) () 很黄很暴力…… edc() ee() Size xce()bytes Wasted x()bytes HighFrequencyHeap ba() e(f) aa() c(f) () () d() 很黄很暴力 eba() ee() Size xa()bytes Wasted x()bytes StubHeap baa() Size x()bytes Virtual Call Stub Heap IndcellHeap Size x()bytes LookupHeap Size x()bytes ResolveHeap Size x()bytes DispatchHeap Size x()bytes CacheEntryHeap bc() Size x()bytes Total size xc()bytes 一共占用了M内存继续看下面的内容我居然发现了个module!!! Module Thunk heaps Module aa Size x()bytes Module e Size x()bytes Module a Size x()bytes Module Size x()bytes =============很黄很暴力====================== Module ee Size x()bytes Module ee Size x()bytes Module eec Size x()bytes Module eec Size x()bytes Total size x()bytes
Total LoaderHeap size xaf()bytes ======================================= 问题基本出来了居然在内存里面有将近万个module!随便dump出来一个看看这里又有一个命令!dumpmodule 随便dump出来一个看类似如下信息 > !dumpmodule ee Name qrtxcw Version= Culture=neutral PublicKeyToken=null Attributes PEFile Assembly cfe LoaderHeap TypeDefToMethodTableMap edcc TypeRefToMethodTableMap edccc MethodDefToDescMap edccc FieldDefToDescMap edcc MemberRefToDescMap edccc FileReferencesMap edcc AssemblyReferencesMap edcc MetaData start address ed ( bytes) 这里可能看不到啥那么我们加一个参数mt来看看 > !dumpmodule mt ee Name qrtxcw Version= Culture=neutral PublicKeyToken=null Attributes PEFile Assembly cfe LoaderHeap TypeDefToMethodTableMap edcc TypeRefToMethodTableMap edccc MethodDefToDescMap edccc FieldDefToDescMap edcc MemberRefToDescMap edccc FileReferencesMap edcc AssemblyReferencesMap edcc MetaData start address ed ( bytes) Types defined in this module MT TypeDef Name
eec x MicrosoftXmlSerializationGeneratedAssemblyXmlSerializationReaderCreateUserResponseData eec x MicrosoftXmlSerializationGeneratedAssemblyXmlSerializerContract Types referenced in this module MT TypeRef Name
eeac x SystemXmlSerializationXmlSerializationReader eb x SystemXmlSerializationXmlSerializerImplementation ebc x MicrosoftProvisioningWebServicesHostedActiveDirectoryCreateUserResponseData ebe x SystemXmlXmlReader fdcc x SystemCollectionsHashtable fa xe SystemObject ec x SystemXmlXmlQualifiedName c x SystemBoolean eab x SystemXmlXmlNameTable 出现了SystemXmlSerialization大家熟悉吗?我们转过头来看debugdiag分析的call stack Call stack sample Address xc Allocation Time since tracking started Allocation Size Bytes Function Source Destination mscorjit!norls_allocatornraAllocNewPage+ mscorjit!norls_allocatornraAlloc+ mscorjit!norls_allocatornraAllocNewPage mscorjit!jitNativeCode+ mscorjit!norls_allocatornraAlloc mscorjit!CILJitcompileMethod+d mscorjit!jitNativeCode xECE mscorjit!CompilerimpExpandInline+aa mscorjit!CompilerfgMorphTree+ mscorjit!CompilerfgMorphStmts+ mscorjit!CompilerfgMorphTree mscorjit!CompilerfgMorphBlocks+ mscorjit!CompilerfgMorphStmts mscorjit!CompilerfgMorph+ mscorjit!CompilerfgMorphBlocks mscorjit!CompilercompCompile+f mscorjit!CompilerfgMorph mscorjit!CompilercompCompile+d mscorjit!CompilercompCompile mscorjit!jitNativeCode+b mscorjit!CompilercompCompile mscorjit!CILJitcompileMethod+d mscorjit!jitNativeCode xEEDFF SystemXmlSerializationTempAssemblyInvokeReader(SystemXmlSerializationXmlMapping SystemXmlXmlReader SystemXmlSerializationXmlDeserializationEvents SystemString) SystemXmlSerializationTempAssemblyInvokeReader(SystemXmlSerializationXmlMapping SystemXmlXmlReader SystemXmlSerializationXmlDeserializationEvents SystemString) SystemXmlSerializationXmlSerializerDeserialize(SystemXmlXmlReader SystemString SystemXmlSerializationXmlDeserializationEvents) SystemXmlSerializationTempAssemblyInvokeReader(SystemXmlSerializationXmlMapping SystemXmlXmlReader SystemXmlSerializationXmlDeserializationEvents SystemString) SystemXmlSerializationXmlSerializerDeserialize(SystemXmlXmlReader SystemString) SystemXmlSerializationXmlSerializerDeserialize(SystemXmlXmlReader SystemString SystemXmlSerializationXmlDeserializationEvents) SystemXmlSerializationXmlSerializerDeserialize(SystemIOStream) SystemXmlSerializationXmlSerializerDeserialize(SystemXmlXmlReader SystemString) MicrosoftProvisioningSdkXmlSerializationProvisioningObjectFactoryConvert[[System__Canon mscorlib][System__Canon mscorlib]](System__Canon) SystemXmlSerializationXmlSerializerDeserialize(SystemIOStream) MicrosoftProvisioningWebServicesServiceBaseSubmit[[System__Canon mscorlib][System__Canon mscorlib]](System__Canon SystemString SystemString Boolean) MicrosoftProvisioningSdkXmlSerializationProvisioningObjectFactoryConvert[[System__Canon mscorlib][System__Canon mscorlib]](System__Canon) MicrosoftProvisioningWebServicesHostedActiveDirectoryServiceCreateOrganization(MicrosoftProvisioningWebServicesHostedActiveDirectoryCreateOrganizationRequest Boolean) xEBEB xAEE SystemWebServicesProtocolsLogicalMethodInfoInvoke(SystemObject SystemObject[]) SystemWebServicesProtocolsWebServiceHandlerInvoke() SystemWebServicesProtocolsLogicalMethodInfoInvoke(SystemObject SystemObject[]) xFFC SystemThreading_TimerCallbackTimerCallback_Context(SystemObject) SystemThreadingExecutionContextRun(SystemThreadingExecutionContext SystemThreadingContextCallback SystemObject) webengine!HashtableIUnknownAddCallback+a webengine!HttpCompletionProcessRequestInManagedCode+a webengine!HttpCompletionProcessRequestInManagedCode+a webengine!HttpCompletionProcessCompletion+e webengine!HttpCompletionProcessRequestInManagedCode webengine!CorThreadPoolWorkitemCallback+ xF xFBC kernel!BaseThreadStart+ 看到这里基本差不多偶认为是exchange内部的代码问题此话怎讲?从头说在SystemXmlSerialization下面有一个by design的bug我们用reflector看XmlSerializer的构造代码 thistempAssembly = cache[defaultNamespace type] if (thistempAssembly == null) { lock (cache) { thistempAssembly = cache[defaultNamespace type] if (thistempAssembly == null) { XmlSerializerImplementation implementation Assembly assembly = TempAssemblyLoadGeneratedAssembly(type defaultNamespace out implementation) if (assembly == null) { thismapping = new XmlReflectionImporter(defaultNamespace)ImportTypeMapping(type null defaultNamespace) thistempAssembly = GenerateTempAssembly(thismapping type defaultNamespace) } else { thismapping = XmlReflectionImporterGetTopLevelMapping(type defaultNamespace) thistempAssembly = new TempAssembly(new XmlMapping[] { thismapping } assembly implementation) } } cacheAdd(defaultNamespace type thistempAssembly) } 为了加快运行速度xmlserializer做了cache在代码中也许我们有N多的type那么每个type在这里都做了一个assembly都把assembly放到了cache中这样本来没问题但是如果type有几千个有几万个那么就有几千几万个temp assembly出现这些assembly都很小也许只有个字节也许是字节但是注意的是内存分配时是按照块来分配的假如说每个块最小为k大小那么即使只分配一个字节的内存我们也要申请一个k的page那么如果我们的小块非常非常多那么我们身子缩小N倍后你会发现内存里面四处都是小窟窿这些窟窿加起来很大但是别人就是不能用因为这k内存必须要连续的块 so当我们发现wwpexe仅仅百兆的时候就报OOM了就是这个原因 关于这个bug可以看msdn这个kbus |