.Net 調式案例—實驗3 內存(Memory)回顧 System.OutOfMemoryException web
今天的調試課程中的主要問題是內存的研究。此次咱們將壓力測試BuggyBits站點,製造高內存使用量並找出緣由。這個實驗有點長,由於我要講述內存問題研究的各個方面。一旦尼知道dump文件的中的一些東西和性能計數器中的一些東西的關聯你就能夠跳過一些方面。但我仍然建議收集性能日誌,這樣比較完整。 數據庫
前面的案例和設置指導 windows
請參看前面發佈的文章。 服務器
設置性能計數器日誌 session
1) 瀏覽http://localhost/BuggyBits/Links.aspx來讓w3wp.exe 進程啓動。 app
2) 打開一個性能計數器,從開始/運行 中輸入:perfmon.exe。 asp.net
3) 右鍵在點擊「性能日誌和警告/日誌計數器」上點擊「新建日誌設置」,在彈出的對話框中輸入:Lab3-Mem。 dom
4) 在接下來彈出的對話框中點擊「添加對象…」。 函數
5) 添加對象「.NET CLR Memory」和「Process」,點擊關閉。 工具
6) 把時間間隔設置成1秒,由於咱們這個例子運行的時間比較短。
7) 修改運行方式,把「<默認>」修改爲「domain\username」(把默認這個東西刪除掉,把你機器的用戶名輸入進去),而後點擊設置密碼,把你的用戶名對應的密碼輸入進去。這樣作的目的是你要以管理員(administrator)或具備對w3wp.exe 調試權限的用戶來運行日誌計數器。不然將得不到.net 計數器的值。
8) 點擊確認,這個將會啓動性能監視器對進程的監控。
問題再現
1) 在命令行中,切換到tinyget的目錄。
2) 運行:tinyget -srv:localhost -uri:/BuggyBits/Links.aspx -loop:4000,注意,若是你發現你的進程由於OutOfMemory異常崩潰了,你能夠稍微下降循環的次數,我這裏設置的這麼高是想獲得好一點的效果。
3) 中止性能計數器。(在運行tinyget後至少要讓他再運行20到30秒)
檢查性能計數器的日誌
1) 在性能監視器中,選擇「系統監視器」這個節點。
2) 點擊工具欄上「查看日誌數據」(像數據庫同樣的那個圖標),而後打開日誌文件,請儘量多的囊括更多的時間。
3) 把窗口下面那些默認的計數器都刪除掉。
4) 添加一些計數器:
·.NET CLR Memory/# Bytes in all Heaps
·.NET CLR Memory/Large Object Heap Size
·.NET CLR Memory/Gen 2 heap size
·.NET CLR Memory/Gen 1 heap size
·.NET CLR Memory/Gen 0 heap size
·Process/Private Bytes
·Process/Virtual Bytes
這些計數器將會在窗口底部顯示出來。
5) 在圖表任意位置上點擊右鍵,選擇「屬性」,在「數據」的選項卡上在比例一欄中選擇0,0000001,確保你能夠在一個屏幕上看到在整個圖表。若是你點擊「突出顯示」那個工具欄的按鈕,那被選中的計數器就會高亮顯示,更有利於知道那個計數器的含義。
Q:這些計數器的最新值是什麼?
A:
Q:比較一下 Virtual Bytes, Private Bytes 和 #Bytes in all Heaps,他們的曲線是否是一致的增加或降低,仍是分歧很大?
A:
這三個曲線 #Bytes in all heaps (底部), Private bytes (中間) 和virtual bytes (頂部)是一致的增加的,這意味着內存的增加是因爲#Bytes in all heaps (.net GC memory)的增加而增加的。
要查找出是什麼類型的「內存泄露」,咱們可使用以下的規則:
a) virtual bytes 增加,Private bytes保持平穩 => 是virtual bytes 泄漏,例如一些組件保留了一些內存,但沒有使用它。=> 使用調試工具來跟蹤下去看看。
b) Private bytes 增加,#Bytes in all heaps 保持平穩 => 本地(原生)或加載器堆(loader heap)泄漏 => 使用調試工具來跟蹤查看,另外看看程序集(assemblies)的數量是否是增長了(看 .net clr loading 下的計數器)。
c) #Bytes in all heaps 和 private bytes 彼此一塊兒增加或降低 => 研究 dotnet GC 堆。
在virtual bytes和 private bytes之間有200-300M的差異,在服務器進程中這是徹底正常的,特別是在一個dotnet進程中,由於有不少內存被保留用來初始化GC 堆(其餘比如dll等這些事情),那些 virtual bytes 的增加也是塊狀的,那也是正常的,由於堆(內存)典型的是以塊申請的。
Q:從這些數據你可否告訴咱們這是本地(原生)內存泄露,仍是.net 內存泄露,或者是在裝載堆(動態程序集)上的內存泄露?
A:經過上面的觀察,這是一個.net 內存泄露。
Q:在日誌記錄的結尾(當tinyget運行結束後),內存保持平穩仍是降低?
A:它仍是保持平穩,就是說在壓力測試後內存沒有被回收。
6) 添加
.NET CLR Memory/# Gen 0 Collections,
.NET CLR Memory/# Gen 1 Collections,
.NET CLR Memory/# Gen 2 Collections
三個計數器。
Q:當tinyget運行完後,收集行爲有發生麼?若是沒有,爲何呢?
A:垃圾收集(GC)僅僅發生在分配內存時或你主動調用GC.Collect()時。這個概念是一個重點,請你記住。在咱們的壓力測試事後,咱們並無兩個條件中的一個,所以沒有人會收集那些內存,除非它必需要這麼作(其餘有需求時,操做系統來作,或dotnet的其餘程序)。
7) 打開任務管理器,在「進程」選項卡上,點擊菜單上的「查看/選擇列」,勾選上「虛擬內存大小」,把「內存使用」和「虛擬內存大小」的值與性能計數器中的「Private Bytes」 和「Virtual Bytes」進行比較,(注意:任務管理器中給出的是以K爲單位的,你須要乘以1024)。
Q:「內存使用」顯示什麼東西?
Q:「虛擬內存大小」顯示什麼東西?
A:在我這裏,這兩個指標在任務管理器顯示了大約746MB,而後當我抓去dump文件時, 內存使用跳到了大約864MB,在這裏的重點是理解這些值得含義。
另外關於性能監視器中的一些解釋:
Private Bytes |
"the current size, in bytes, of memory that this process has allocated that cannot be shared with other processes." |
Virtual Bytes |
"the current size, in bytes, of the virtual address space the process is using. Use of virtual address space does not necessarily imply corresponding use of either disk or main memory pages. Virtual space is finite, and the process can limit its ability to load libraries." |
Working Set |
"the current size, in bytes, of the Working Set of this process. The Working Set is the set of memory pages touched recently by the threads in the process. If free memory in the computer is above a threshold, pages are left in the Working Set of a process even if they are not in use. When free memory falls below a threshold, pages are trimmed from Working Sets. If they are needed they will then be soft-faulted back into the Working Set before leaving main memory." |
#Bytes in all heaps |
"This counter is the sum of four other counters; Gen 0 Heap Size; Gen 1 Heap Size; Gen 2 Heap Size and the Large Object Heap Size. This counter indicates the current memory allocated in bytes on the GC Heaps." |
Gen 0 heap size |
"This counter displays the maximum bytes that can be allocated in generation 0 (Gen 0); its does not indicate the current number of bytes allocated in Gen 0. A Gen 0 GC is triggered when the allocations since the last GC exceed this size. The Gen 0 size is tuned by the Garbage Collector and can change during the execution of the application. At the end of a Gen 0 collection the size of the Gen 0 heap is infact 0 bytes; this counter displays the size (in bytes) of allocations that would trigger the next Gen 0 GC. This counter is updated at the end of a GC; its not updated on every allocation." |
Gen 1 heap size |
"This counter displays the current number of bytes in generation 1 (Gen 1); this counter does not display the maximum size of Gen 1. Objects are not directly allocated in this generation; they are promoted from previous Gen 0 GCs. This counter is updated at the end of a GC; its not updated on every allocation." |
Gen 2 heap size |
"This counter displays the current number of bytes in generation 2 (Gen 2). Objects are not directly allocated in this generation; they are promoted from Gen 1 during previous Gen 1 GCs. This counter is updated at the end of a GC; its not updated on every allocation." |
LOH size |
"This counter displays the current size of the Large Object Heap in bytes. Objects greater than |
這裏有一些注意的事情:
1) Gen 0 heap size 顯示的是Gen 0 的預算大小,不是在Gen 0 中的全部對象的大小。
2) Gen 0,1,2 和LOH計數器是在每次收集後才更新的,不是在每次分配後。
3) 對於LOH的解釋,20K是針對1.0 版本的,在1.1 和2.0 後大對象的大小是大於85000字節的。
4) 這個進程的工做集環境是由一些RAM中的頁面文件組成,這些頁面文件是由該進程建立的,這個進程的private bytes 是沒有什麼事情能夠作的(咱們在查看任務管理器的時候,咱們經常看內存使用量,它並不能表明什麼),進城使用的內存量可能多於或少於private bytes。對於winform的應用程序(沒有服務進程的)有很大的差異,當你最小化一個winform程序時,程序會對內存進程對齊操做。而對於服務程序,例如asp.net 不會這樣,private bytes 和工做集會保持在一樣的範圍裏面,不會改變。
獲得一個dump文件
1) 在命令提示符下面,切換到調試器目錄,運行:「adplus -hang -pn w3wp.exe -quiet」。
在WinDbg中打開dump文件
1) 在WinDbg中打開dump文件。
2) 設置符號文件路徑和加載sos。
Q:dump文件多少大?在windows資源管理器中看看就知道了。
A: 864 096 k
Q:這個文件的大小和Private Bytes, Virtual Bytes 和 # Bytes in all Heaps 比較一下,看看怎麼樣?
A:看起來,它們都是差很少大的。
檢查內存,想一想內存去哪裏了
1) 運行 !address –summary (這個命令給你一個內存使用的概要),讓你本身熟悉一下這些輸出。能夠查看WinDbg 的幫助看看 !address。
0:000> !address -summary
-------------------- Usage SUMMARY --------------------------
TotSize ( KB) Pct(Tots) Pct(Busy) Usage
373b7000 ( 904924) : 21.58% 85.85% : RegionUsageIsVAD
bfa89000 ( 3140132) : 74.87% 00.00% : RegionUsageFree
76e6000 ( 121752) : 02.90% 11.55% : RegionUsageImage
67c000 ( 6640) : 00.16% 00.63% : RegionUsageStack
0 ( 0) : 00.00% 00.00% : RegionUsageTeb
144a000 ( 20776) : 00.50% 01.97% : RegionUsageHeap
0 ( 0) : 00.00% 00.00% : RegionUsagePageHeap
1000 ( 4) : 00.00% 00.00% : RegionUsagePeb
1000 ( 4) : 00.00% 00.00% : RegionUsageProcessParametrs
2000 ( 8) : 00.00% 00.00% : RegionUsageEnvironmentBlock
Tot: ffff0000 (4194240 KB) Busy: 40567000 (1054108 KB)
-------------------- Type SUMMARY --------------------------
TotSize ( KB) Pct(Tots) Usage
bfa89000 ( 3140132) : 74.87% :
834e000 ( 134456) : 03.21% : MEM_IMAGE
95a000 ( 9576) : 00.23% : MEM_MAPPED
378bf000 ( 910076) : 21.70% : MEM_PRIVATE
-------------------- State SUMMARY --------------------------
TotSize ( KB) Pct(Tots) Usage
34bea000 ( 864168) : 20.60% : MEM_COMMIT
bfa89000 ( 3140132) : 74.87% : MEM_FREE
b97d000 ( 189940) : 04.53% : MEM_RESERVE
Largest free region: Base 80010000 - Size 7fefa000 (2096104 KB)
這裏有很是多的信息,但全部的這些都不是這麼明顯的。相信我,讓咱們花一些時間在這裏。在任何一個案例中,我用這個來幫助我指出我須要查看哪些地方,因此我不介意它要花費多少,即便就是一個概述的結果。
一些要注意的地方:
上面一屏顯示了按照類型不一樣而分類顯示的由進程使用的內存。第一部分是按照區域類型來劃分的,它按照什麼樣子的分配類型告訴你信息。最常遇到的一個類型是VAD = Virtual Alloc, Image = dlls 和 exes,Heap = heaps the process owns,從WinDbg的幫助中能夠獲得更多的信息。接着下面是按IMAGE, MAPPED 或 PRIVATE 的類型來列出,最後一部分是按已提交(also committed,就是指實際已經分配的)或保留(reserved)的方式來列出它們。
Tot: ffff0000 (4 194 240 kb) :的意思是我總共有4GB的虛擬內存地址空間提供給這個應用程序。32位系統上,你能夠尋地4GB的空間,典型的是2GB的用戶模式的內存空間,因此通常你會看到2GB而不是這裏的4GB,在64位上,運行一個32位的進程會獲得徹底的4GB的空間,因此我這裏看到的是4GB。
Busy: 40567000 (1 054 108 kb) 是咱們已經使用的(已經分配的)。
MEM_PRIVATE是一個私有的內存,它不和其餘進程共享內存,不是映射到文件的內存。不要把這個和性能計數器中的Private Bytes混淆。這裏的MEM_PRIVATE 是保留+已提交(即已分配的)(reserved + committed)的字節數,另外那個Private Bytes 是申請/已提交(allocated/committed)的字節數。
MEM_PRIVATE 是已經提交(已經分配)的內存(不必定是 private的),這個多是最接近你獲得的Private Bytes的。
MEM_RESERVE 是已經保留的,但沒有實際分配的,未提交的內存。全部已經分配的內存也是定義爲保留的,因此若是你查看全部保留的內存(最接近你獲得的virtual bytes),你必須加上MEM_COMMIT和 MEM_RESERVE,它是顯示在Busy 中的那個數字。你本身把數字加上後比對一下看看。
Q:哪一個值最能表明以下兩個指標?
·Private Bytes A: MEM_COMMIT
·Virtual Bytes A: Busy
Q:大部分的內存都去哪裏了?(哪一個區域)
A:在這裏,大約904MB是爲VAD保留的,VAD是dotnet對象存放的地方,由於GC堆是virtual allocs 分配的。
Q:Busy,Pct(Busy),Pct(Tots)是什麼意思?
A:Pct(Tots) 顯示的是整個虛擬地址空間中分配給不一樣區域類型的百分比。Pct(Busy)顯示的是保留的內存中分配給不一樣區域的百分比。Pct(busy) 很顯然是我最關心的一個。
Q:MEM_IMAGE 是什麼意思?
A:從幫助文件中咱們知道:這個是表示從一個可執行的映射文件的一部分映射到的內存。換句話說 就是dll 或一個exe 文件的內存映射。
Q:哪一個區域的.net 內存是適宜的,爲何?
A:在RegionUsageIsVAD,理由如上。
從性能計數器中咱們看到#Bytes in all Heaps 跟隨着Private bytes的增加而增加,那說明了內存的增長几乎都是.net 的使用而增長的,進而咱們轉化爲爲何.net 的GC堆(heap)始終在增加。
檢查.net GC 堆(heap)
1) 運行 !eeheap –gc 來查看.net GC 堆的大小
0:000> !eeheap -gc
Number of GC Heaps: 2
------------------------------
Heap 0 (001aa148)
generation 0 starts at 0x32f0639c
generation 1 starts at 0x32ae3754
generation 2 starts at 0x02eb0038
ephemeral segment allocation context: none
segment begin allocated size
001bfe10 7a733370 7a754b98 0x00021828(137256)
001b0f10 790d8620 790f7d8c 0x0001f76c(128876)
…..
Large object heap starts at 0x0aeb0038
segment begin allocated size
0aeb0000 0aeb0038 0aec0b28 0x00010af0(68336)
Heap Size 0x15fd1310(368907024)
------------------------------
Heap 1 (001ab108)
generation 0 starts at 0x36e665bc
generation 1 starts at 0x36a28044
generation 2 starts at 0x06eb0038
ephemeral segment allocation context: none
segment begin allocated size
06eb0000 06eb0038 0aea58d4 0x03ff589c(67066012)
……
Large object heap starts at 0x0ceb0038
segment begin allocated size
0ceb0000 0ceb0038 0ceb0048 0x00000010(16)
Heap Size 0x15ab1570(363533680)
------------------------------
GC Heap Size 0x2ba82880(732440704)
Q:總共有多少個Heap,爲何?
A:這裏有兩個堆,由於咱們運行在多核進程模型中。
Q:有多少內存被保存在了.net GC 堆中?拿#Bytes in all Heaps比較一下。
A:GC的堆大小是:GC Heap Size 0x2ba82880(732 440 704),它和性能計數器中的bytes in all heaps很接近。
Q:large object heap 上有多少內存?提示:把large object heap段上的合計加起來,和性能計數器中的Large Object Heap Size 比較一下。
A:它是很是小的,因此LOH看起來不是問題所在,大小是68 336 + 16 bytes
2) 運行 !dumpheap –stat 來輸出全部的以統計式樣表示的.net 對象。
Q:查看 5 到10個使用了大部份內存的對象,思考一下是什麼泄露了?
A:
66424cf4 37 57276 System.Web.Caching.ExpiresEntry[]
663b0cdc 4001 192048 System.Web.SessionState.InProcSessionState
7912d8f8 3784 255028 System.Object[]
7912d9bc 820 273384 System.Collections.Hashtable+bucket[]
6639e4c0 4037 290664 System.Web.Caching.CacheEntry
0fe11cf4 36000 576000 Link
790fdc5c 36161 723220 System.Text.StringBuilder
001a90c0 1105 7413924 Free
790fd8c4 51311 721773112 System.String
Total 163943 objects
大部分的內存是被strings用掉了,這個不太正常,雖然strings 在應用中是最多見的,可是大約721MB的顯然有點怪異,而且有3600個Links(不管它們是什麼),看起來有點奇怪。特別是由於有差很少數量的stringbuilds 出如今dump中。
Q:「size」那個行顯示了什麼?例如,「size」這行包含了什麼?
A:若是咱們用命令!do 把Link 對象輸出來,咱們看到有一個指針指向stringbuilder(url)和一個指針指向string(name),link對象的大小是16B,這個大小僅僅包含了指針的大小和其餘一些開銷(methos table 等)。
若是你運行 !objsize ,你會看見大小是高達20144 B ,這個大小是包含成員變量的,好比Link對象的大小和它引用的全部對象。
你看到了什麼經過!dumpheap 輸出的16B的每個link。它不包含成員變量的大小是由一些不一樣緣由的:
1)它將要花費很長的時間去計算大小。
2) 一些對象(假如是A和B)可能都指向C對象,若是你使用 !objsize 計算A 和B的大小,他們都會包含C的大小,因此size這個列的值會變得很複雜難以計算。
3) 在這個例子中Link的大小size看起來彷佛是正常的。由於一個link對象包含一個url和一個name。可是若是一個web 控件可能會包含一個成員變量 _parent ,若是你運運行 !objsize ,這樣就會包含父對象(page)那就顯然是不合適的。
0:000> !do 371d44cc
Name: Link
MethodTable: 0fe11cf4
EEClass: 0fde5824
Size: 16(0x10) bytes
(C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\
Temporary ASP.NET Files\buggybits\b27906cc\f5f91899\App_Code.wbwztx_4.dll)
Fields:
MT Field Offset Type VT Attr Value Name
790fdc5c 4000006 4 ...ext.StringBuilder 0 instance 371d44dc url
790fd8c4 4000007 8 System.String 0 instance 02f13cd8 name
0:000> !objsize 371d44cc
sizeof(371d44cc) = 20144 ( 0x4eb0) bytes (Link)
一般,我不推薦馬上查看在你的這個很是簡單的dump文件中,在該命令輸出的底部的strings,由於:
· strings 這一行的「size」是實際的字符串string的有內容的真實大小。若是你和DataSet比較,這個「size」只是包含了行和列的指針,並無包含行和列的內存。因此DataSet這個對象的大小几乎老是很是的小的。
· string 字符串在大部分的對象中幾乎是葉子節點,例如,dataset包含字符串,aspx頁面包含字符串,session 變量也包含字符串。因此,在一個應用中幾乎都是字符串。
然而在這個例子中,字符串有這麼多,佔有了那麼多的內存。若是咱們不查到其餘一些阻止了咱們的東西,那咱們可能就要沿着string 這條路走下去了。
3) 把各類不一樣大小的string 都輸出來,找出哪些string是愈來愈大的。(裏面可能有一些討厭的事和錯誤,因此你要嘗試不一樣的大小來找出那些是愈來愈大的)
獲得string的 MT(method table),!dumpheap –stat 的輸出結果的第一列。
!dumpheap -mt <string MT> -min 85000 -stat
!dumpheap -mt <string MT> -min 10000 -stat
!dumpheap -mt <string MT> -min 20000 -stat
!dumpheap -mt <string MT> -min 30000 -stat
!dumpheap -mt <string MT> -min 25000 -stat
Q:大部分的string’在一個什麼樣的範圍內?
A:在 20000 和 25000 字節之間。
4)把那個範圍內的string 輸出來。
!dumpheap -mt <string MT> -min 20000 -max 25000
在這裏,它們中的大部分是如出一轍的大小的,這是一個指引咱們向下前進的線索。
0:000> !dumpheap -mt 790fd8c4 -min 20000 -max 25000
------------------------------
Heap 0
Address MT Size
02f1412c 790fd8c4 20020
02f2d96c 790fd8c4 20020
02f327c4 790fd8c4 20020
02f3761c 790fd8c4 20020
02f3c474 790fd8c4 20020
02f412cc 790fd8c4 20020
02f46124 790fd8c4 20020
02f4af7c 790fd8c4 20020
02f4fdd4 790fd8c4 20020
02f54c2c 790fd8c4 20020
...
5)把它們中的一些輸出來看看裏面是什麼
!do <address of string> ,地址是 !dumpheap -mt 輸出的第一列。
0:000> !do 02f327c4
...
String: http://www.sula.cn
...
Q:這些string裏面包含的是什麼?
A:好像link.aspx 頁面顯示了link對象。
6)揀幾個,看看它們被根化(rooted)到哪裏(即爲何它們不會被回收)。注意你可能須要嘗試不一樣的幾個才行。
!gcroot <address of string>
0:000> !gcroot 02f327c4
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 16 OSTHread 1948
Scan Thread 20 OSTHread 1b94
Scan Thread 21 OSTHread 1924
Scan Thread 22 OSTHread 188c
Scan Thread 14 OSTHread 1120
Scan Thread 24 OSTHread 13f8
Finalizer queue:Root:02f327a0(Link)->
02f327b0(System.Text.StringBuilder)->
02f327c4(System.String)
Q:它們被根化到哪裏?爲何?
A:這個string是一個string builder 類型的成員變量,它表現的是一個link的成員變量(url),link 對象被根化在終結器隊列中,那就是說他正在等待被終結。
檢查終結器隊列(finalizer queue)和終結線程(finalizer thread)
1)查看終結器隊列
!finalizequeue
0:000> !finalizequeue
SyncBlocks to be cleaned up: 0
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
----------------------------------
------------------------------
Heap 0
generation 0 has 221 finalizable objects (0f44a764->0f44aad8)
generation 1 has 0 finalizable objects (0f44a764->0f44a764)
generation 2 has 45 finalizable objects (0f44a6b0->0f44a764)
Ready for finalization 18009 objects (0f44aad8->0f45c43c)
------------------------------
Heap 1
generation 0 has 338 finalizable objects (0f45d840->0f45dd88)
generation 1 has 4 finalizable objects (0f45d830->0f45d840)
generation 2 has 36 finalizable objects (0f45d7a0->0f45d830)
Ready for finalization 17707 objects (0f45dd88->0f46f234)
Statistics:
MT Count TotalSize Class Name
663a1fc8 1 12 System.Web.Configuration.ImpersonateTokenRef
79116758 1 20 Microsoft.Win32.SafeHandles.SafeTokenHandle
791037c0 1 20 Microsoft.Win32.SafeHandles.SafeFileMappingHandle
79103764 1 20 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle
6639c104 1 20 System.Web.PerfInstanceDataHandle
663f6b5c 1 28 System.Web.Security.FileSecurityDescriptorWrapper
663a105c 1 32 System.Web.Compilation.CompilationMutex
7910b630 2 40 System.Security.Cryptography.SafeProvHandle
79112728 5 100 Microsoft.Win32.SafeHandles.SafeWaitHandle
790fe704 2 112 System.Threading.Thread
7910a5c4 2 120 System.Runtime.Remoting.Contexts
...
Q:在這個命令的輸出中列出了什麼對象?
A:全部具備終結/析構器的都被註冊到終結器隊列中,當對象被垃圾收集時,終結器會運行析構函數,不然在dispose函數中終結過程會掛起。
Q:有多少個對象是出於「ready for finalization」,它是什麼意思?
A:大約有36000個,這些對象是要被垃圾收集的,正在等待被終結。若是ready for finalization大於0 但沒有顯示任何信息,這是一個說明終結器線程被堵塞的最好時機。因此這些對象被堵住了等待終結,他們消耗了大部分的內存。
2) 找出終結線程,瞭解它正在幹什麼,運行!threads ,在列出的線程中查找帶有「(Finalizer)」的線程。
3) 切換到終結線程,檢查託管的和本地(原生)的調用堆棧。
~5s (把5 替換成真實的終結線程(finalizer thread)的ID號)
kb 2000
!clrstack
0:000> !threads
...
20 2 1b94 001ac2c0 200b220 Enabled 00000000:00000000 001ccc80 0 MTA (Finalizer)
...
0:020> !clrstack
OS Thread Id: 0x1b94 (20)
ESP EIP
02a0f8fc 7d61cca8 [HelperMethodFrame: 02a0f8fc] System.Threading.Thread.SleepInternal(Int32)
02a0f950 0fe90ce8 Link.Finalize()
02a0fc1c 79fbcca7 [ContextTransitionFrame: 02a0fc1c]
02a0fcec 79fbcca7 [GCFrame: 02a0fcec]
Q:什麼對象正在被終結?
A:看起來是一個link 對象。
Q:它正在幹什麼? 爲何這個會致使高內存使用率?
A:終結link對象的終結器線程由於sleep 被堵住了。意味着終結器被堵住,進程中沒有東西能夠被終結。於是等待終結的進程都會仍然在內存中直到終結器醒來它們被終結爲止。
查看代碼,確認上面的分析
打開Link.cs 的代碼,看看析構/終結(destructor/finalizer)的有疑問的代碼。