天天看點

分析性能瓶頸 — 調試OutOfMemoryException

為了重制這個問題,我們先來準備一下資料,用下面兩個DOS指令就可以準備好這些資料了:

1.   dir /s /b c:\windows > d:\test.txt

2.   FOR /L %i IN (1,1,100) DO type test.txt >> testdata.txt

第一個指令将Windows檔案夾裡面所有子檔案夾的檔案清單都輸出到test.txt檔案裡,第二個指令循環100遍,将test.txt檔案内容追加到testdata.txt裡面,這樣就可以生成好幾百兆大小的文本檔案了。

 (318.1688): CLR exception - code e0434352 (first chance)

… …

#

# OutOfMemeoryException已經抛出了

 (318.1688): CLR exception - code e0434352 (!!! second chance !!!)

eax=0017eb74 ebx=00000005 ecx=00000005 edx=00000000 esi=0017ec20 edi=005595e0

eip=753b9617 esp=0017eb74 ebp=0017ebc4 iopl=0         nv up ei pl nz ac pe nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000             efl=00000216

KERNELBASE!RaiseException+0x54:

753b9617 c9              leave

0:000> .loadby sos clr

0:000> !pe

Exception object: 744a8e84

Exception type:   System.OutOfMemoryException

Message:          <none>

InnerException:   <none>

StackTrace (generated):

SP       IP       Function

# 不出所料,StreamReader裡面使用StringBuilder将文本檔案讀入到一個字元串裡。而StringBuilder是動态

# 配置設定記憶體的。

    0017ED38 5713E61E mscorlib_ni!System.Text.StringBuilder.ToString()+0x1e

    0017ED64 57121991 mscorlib_ni!System.IO.StreamReader.ReadToEnd()+0x7d

    0017ED78 002F0136 ConsoleApplication1!ConsoleApplication1.Program.Main(System.String[])+0xc6

StackTraceString: <none>

HResult: 8007000e

# 使用AnalyzeOOM來分析一下原因,看看是GC哪一個記憶體區域導緻了這個異常。

0:000> !ao

# 找到了,是大對象堆(Large Object Heap - LOH),GC在嘗試申請一個1.5G的記憶體空間時

# 遭拒。

Managed OOM occured after GC #312 (Requested to allocate 1649667816 bytes)

Reason: Didn't have enough memory to allocate an LOH segment

Detail: LOH: Failed to reserve memory (1652555776 bytes)

# 使用!eeheap –gc指令找到GC裡,各個generation的堆的起始位址和結束位址

0:000> !eeheap -gc

Number of GC Heaps: 1

generation 0 starts at 0x744a8e78

generation 1 starts at 0x7446a408

generation 2 starts at 0x01911000

ephemeral segment allocation context: none

 segment     begin allocated size

01910000 01911000 0290aee0 0xff9ee0(16752352)

03fd0000 03fd1000 04fcd3ec 0xffc3ec(16761836)

73ca0000 73ca1000 744aae84 0x809e84(8429188)

Large object heap starts at 0x02911000

# LOH的起始位址找到了,0x02911000就是LOH的起始位址

# 已經配置設定了0x02913240個位元組,是以結束位址就是

# 0x02911000 + 0x02913240

02910000 02911000 02913240 0x2240(8768)

Total Size:              Size: 0x629424c0 (1653875904) bytes.

------------------------------

GC Heap Size:            Size: 0x629424c0 (1653875904) bytes.

# 使用dumpheap看一下LOH當中各個對象的記憶體配置設定情況。

# dumpheap的參數中,0x02911000是要檢視的記憶體的起始位址,

# 而0x02911000 + 0x02913240是檢視的結束位址

0:000> !dumpheap -stat 02911000 02911000+02913240

total 0 objects

Statistics:

      MT    Count    TotalSize Class Name

00529748        4           84      Free

57166c28        3         8720 System.Object[]

# 不出所料,StringBuilder和Char[]對象最多,但是

# Char[]的數組大小有171M之多。

571afb78     1119        31332 System.Text.StringBuilder

571b1d88     1120     17933440 System.Char[]

Total 2246 objects

# 使用!dumpheap的-mt參數看看char[]數組的詳細記憶體配置設定情況

# 571b1d88這個參數,來自于前面的!dumpheap指令的輸出(注意高亮顯示的地方)

# 這個參數告訴dumpheap指令,隻檢索char[]數組(Method table是571b1d88)

# 的情況。

0:000> !dumpheap -mt 571b1d88 02911000 02911000+02913240

 Address       MT     Size

03fd1000 571b1d88    16012    

05222c90 571b1d88    16012    

Total 1120 objects

# 随便選一個對象(注意上面一個高亮顯示的文本),看看它到底被誰引用了,

# 導緻GC一直不釋放它,畢竟我電腦的有2G的實體記憶體,讀取幾百兆的檔案,

# 就觸發了OutOfMemoryException,的确有點離譜。

0:000> !gcroot 03fd1000

Note: Roots found on stacks may be false positives. Run "!help gcroot" for

more info.

Scan Thread 0 OSTHread 1688

ESP:17eca4:Root: 0191d2e0(System.Text.StringBuilder)->

 744a4fd0(System.Text.StringBuilder)->

 … …

 5de44814(System.Text.StringBuilder)->

Command cancelled at the user's request.

 03fd1000(System.Char[])

# 再看看0x0191d2e0這個StringBuilder對象都在哪些地方被引用到了,

# 根據GC的實作,堆棧上各個函數的局部變量是當作root來處理的

0:000> !gcroot 0191d2e0

ESP:17eca4:Root: 0191d2e0(System.Text.StringBuilder)

ESP:17eca8:Root: 0191d2e0(System.Text.StringBuilder)

ESP:17ecb4:Root: 0191d2e0(System.Text.StringBuilder)

ESP:17ecbc:Root: 0191d2e0(System.Text.StringBuilder)

ESP:17ed3c:Root: 0191d2e0(System.Text.StringBuilder)

ESP:17ed58:Root: 0191d2e0(System.Text.StringBuilder)

Scan Thread 2 OSTHread ce8

# 從前面的輸出,随便找兩個對象(前面标注的文本),看看它們都是哪些函數的局部變量

# 例如17ed58這個位址介于System.IO.StreamReader.ReadToEnd()和

# System.Text.StringBuilder.ToString()之間,也就是StreamReader.ReadToEnd()

# 這個函數定義的,至此,基本上我們可以認為已經找到影響性能的元兇了。

0:000> !dumpstack

OS Thread Id: 0x1688 (0)

Current frame: KERNELBASE!RaiseException+0x54

ChildEBP RetAddr Caller, Callee

0017eb7c 753b9617 KERNELBASE!RaiseException+0x54, calling ntdll!RtlRaiseException

0017eba0 5888bc5e clr!DllUnregisterServerInternal+0x15c62, calling clr!DllUnregisterServerInternal+0xa55b

0017ebac 588fa99c clr!LogHelp_TerminateOnAssert+0x2df44, calling clr!DllUnregisterServerInternal+0x15c45

0017ebbc 58871bd0 clr+0x1bd0, calling clr+0x1b8b

0017ebc4 588fac08 clr!LogHelp_TerminateOnAssert+0x2e1b0, calling KERNEL32!RaiseException

0017ec54 5896ab0b clr!CopyPDBs+0x4abd, calling clr!LogHelp_TerminateOnAssert+0x2e03e

0017ec90 58b27c9e clr!CorLaunchApplication+0x11756, calling clr!CopyPDBs+0x4a78

0017ecdc 588908f6 clr!CoUninitializeEE+0x272e, calling clr!GetMetaDataInternalInterface+0xde18

0017ed30 5713e61e (MethodDesc 56f0c09c +0x1e System.Text.StringBuilder.ToString()), calling 00242350

0017ed5c 57121991 (MethodDesc 56efff40 +0x7d System.IO.StreamReader.ReadToEnd())

0017ed70 002f0136 (MethodDesc 00253840 +0xc6 ConsoleApplication1.Program.Main(System.String[]))

0017edd4 588721db clr+0x21db

0017ede4 58894a2a clr!CoUninitializeEE+0x6862, calling clr+0x21a8

0017f91c 589daf00 clr!CorExeMain+0x1c, calling clr!GetCLRFunction+0xd6a

0017f954 718455ab mscoreei!CorExeMain+0x38

0017f960 6f667f16 MSCOREE!CreateConfigStream+0x13f

0017f970 6f664de3 MSCOREE!CorExeMain+0x8, calling MSCOREE!CorExeMain+0x2f14

0017f978 75d41194 KERNEL32!BaseThreadInitThunk+0x12

0017f984 7723b495 ntdll!RtlInitializeExceptionChain+0x63

0017f9c4 7723b468 ntdll!RtlInitializeExceptionChain+0x36, calling ntdll!RtlInitializeExceptionChain+0x3c

本文轉自 donjuan 部落格園部落格,原文連結: http://www.cnblogs.com/killmyday/archive/2010/07/03/1770616.html  ,如需轉載請自行聯系原作者