电脑故障

位置:IT落伍者 >> 电脑故障 >> 浏览文章

Out Of Memory的分析及诊断方法


发布日期:2019/8/24
 

首先什么是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 contextxbcbf 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 badeebeaff) 很黄很暴力…… edcee) Size xce)bytes

Wasted x)bytes

HighFrequencyHeap baefaacfd) 很黄很暴力 ebaee) 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

上一篇:Request获取url各种信息的方法

下一篇:获取源页的公共属性值