首先什麼是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
From:http://tw.wingwit.com/Article/program/net/201311/12615.html