日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
詳解.NET內(nèi)存管理機制與垃圾回收

探討.NET內(nèi)存管理機制與垃圾回收,也是對.NET平臺編程效率的一種提高。了解.NET內(nèi)存管理機制對今后對內(nèi)存的操作,具有十分重要的意義。

1. Stack和Heap

每個線程對應(yīng)一個stack,線程創(chuàng)建的時候CLR為其創(chuàng)建這個stack,stack主要作用是記錄函數(shù)的執(zhí)行情況。值類型變量(函數(shù)的參數(shù)、局部變量等非成員變量)都分配在stack中,引用類型的對象分配在heap中,在stack中保存heap對象的引用指針。GC只負責(zé)heap對象的釋放,heap內(nèi)存空間管理

Heap內(nèi)存分配

除去pinned object等影響,heap中的內(nèi)存分配很簡單,一個指針記錄heap中分配的起始地址,根據(jù)對象大小連續(xù)的分配內(nèi)存

Stack結(jié)構(gòu)

每個函數(shù)調(diào)用時,邏輯上在thread stack中會產(chǎn)生一個幀(stack frame),函數(shù)返回時對應(yīng)的stack frame被釋放掉
用個簡單的函數(shù)查看執(zhí)行時CLR對棧的處理情況:

 
 
 
 
  1. static void Main(string[] args)  
  2. {  
  3. int r = Sum(2, 3, 4, 5, 6);  
  4. }  
  5. private static int Sum(int a, int b, int c, int d, int e)  
  6. {  
  7. return a + b + c + d + e;  

JIT編譯后主要匯編代碼如下(其他的情況下匯編代碼可能有所差別,但用這個簡單函數(shù)大致看下棧的管理已經(jīng)足夠):

 
 
 
 
  1. ;====函數(shù)Main====  
  2. push4 ;第3個參數(shù)到最后一個參數(shù)壓棧  
  3. push5  
  4. push6  
  5. movedx,3 ;第1、第2個參數(shù)分別放入ecx、edx寄存器  
  6. movecx,2   
  7. calldword ptr ds:[00AD96B8h];調(diào)用函數(shù)Sum,執(zhí)行call的時候返回地址(即下面這條mov語句的地址)自動壓棧了  
  8. movdword ptr [ebp-0Ch],eax ;將函數(shù)返回值設(shè)置到局部變量r中(函數(shù)調(diào)用結(jié)束返回值在eax寄存器中)  
  9.  
  10. ;====函數(shù)Sum====  
  11. pushebp ;保存原始ebp寄存器  
  12. movebp,esp ;將當(dāng)前棧指針保存在ebp中,后面使用ebp對參數(shù)和局部變量尋址  
  13. subesp,8 ;分配兩個局部變量  
  14. movdword ptr [ebp-4],ecx ;第1個參數(shù)放入局部變量  
  15. movdword ptr [ebp-8],edx ;第2個參數(shù)放入局部變量  
  16. ...... ;CLR的檢查代碼  
  17. moveax,dword ptr [ebp-4];a + b + c + d + e  
  18. addeax,dword ptr [ebp-8];第1個參數(shù)+第2個參數(shù)(2+3)  
  19. addeax,dword ptr [ebp+10h];+第3個參數(shù)(4)  
  20. addeax,dword ptr [ebp+0Ch];+第4個參數(shù)(5)  
  21. addeax,dword ptr [ebp+8];+第5個參數(shù)(6)  
  22. movesp,ebp;恢復(fù)棧指針(局部變量被釋放了)  
  23. popebp;恢復(fù)原始的ebp寄存器值  
  24. ret0Ch ;函數(shù)返回. 1: 返回地址自動出棧; 2: esp減去0Ch(12個字節(jié)),即從棧中清除調(diào)用參數(shù); 3: 返回值在eax寄存器中執(zhí)行時刻的stack狀態(tài)如下(棧基地址為高端地址,棧頂為低端地址): 

Stack狀態(tài)變化過程:

a). 調(diào)用者將第3、第4、第5個參數(shù)壓棧,第1、第2個參數(shù)分別放入ecx、edx寄存器

b). call指令調(diào)用函數(shù)Sum,并自動將函數(shù)返回地址壓棧,代碼跳轉(zhuǎn)到函數(shù)Sum開始執(zhí)行

c). 函數(shù)Sum先將寄存器ebp壓棧保存,并將esp放入ebp,用于后面對參數(shù)和局部變量尋址

d). 定義局部變量以及省略掉的是額外代碼,跟Sum函數(shù)業(yè)務(wù)無關(guān)

e). 執(zhí)行加法操作,結(jié)果保存在eax寄存器中

f). 恢復(fù)esp寄存器,這樣函數(shù)Sum中所有的局部變量以及其他壓棧操作全部釋放出來

g). 原始ebp的值出棧,恢復(fù)ebp,這樣棧完全恢復(fù)到進入Sum函數(shù)調(diào)用時的狀態(tài)

h). ret指令執(zhí)行函數(shù)返回,返回值在eax寄存器中,返回地址為call指令壓棧的地址,返回地址自動出棧。0Ch指示處理器在函數(shù)返回時釋放棧中12個字節(jié),即由被調(diào)用者清除壓棧的參數(shù)。函數(shù)返回之后,本次Sum調(diào)用的棧分配全部釋放

這種調(diào)用約定類似__fastcall

結(jié)合引用類型變量、值類型的ref參數(shù),下面代碼簡化的stack狀態(tài)如下:

代碼:

 
 
 
 
  1. public static void Run(int i)  
  2. {  
  3. int j = 9;  
  4. MyClass1 c = new MyClass1();  
  5. c.x = 8;  
  6. int result = Sum(i, 5, ref j, c);  
  7. }  
  8.  
  9. public static int Sum(int a, int b, ref int c, MyClass1 obj)  
  10. {  
  11. int r = a + b + c + obj.x;  
  12. return r;  
  13. }  
  14.  
  15. public class MyClass1  
  16. {  
  17. public int x;  

Stack狀態(tài)

任何時候引用類型都分配在heap中,在stack中只是保存對象的引用地址。Run函數(shù)執(zhí)行完畢之后,heap中的MyClass1對象c成為可回收的垃圾對象,在GC時進行回收

#p#

2. Mark-Compact 標(biāo)記壓縮算法

簡單把.NET的GC算法看作Mark-Compact算法

階段1: Mark-Sweep 標(biāo)記清除階段

先假設(shè)heap中所有對象都可以回收,然后找出不能回收的對象,給這些對象打上標(biāo)記,最后heap中沒有打標(biāo)記的對象都是可以被回收的

階段2: Compact 壓縮階段

對象回收之后heap內(nèi)存空間變得不連續(xù),在heap中移動這些對象,使他們重新從heap基地址開始連續(xù)排列,類似于磁盤空間的碎片整理。

Heap內(nèi)存經(jīng)過回收、壓縮之后,可以繼續(xù)采用前面的heap內(nèi)存分配方法,即僅用一個指針記錄heap分配的起始地址就可以

主要處理步驟:將線程掛起=>確定roots=>創(chuàng)建reachable objects graph=>對象回收=>heap壓縮=>指針修復(fù)
可以這樣理解roots:heap中對象的引用關(guān)系錯綜復(fù)雜(交叉引用、循環(huán)引用),形成復(fù)雜的graph,roots是CLR在heap之外可以找到的各種入口點。GC搜索roots的地方包括全局對象、靜態(tài)變量、局部對象、函數(shù)調(diào)用參數(shù)、當(dāng)前CPU寄存器中的對象指針(還有finalization queue)等。主要可以歸為2種類型:已經(jīng)初始化了的靜態(tài)變量、線程仍在使用的對象(stack+CPU register)

Reachable objects:指根據(jù)對象引用關(guān)系,從roots出發(fā)可以到達的對象。例如當(dāng)前執(zhí)行函數(shù)的局部變量對象A是一個root object,他的成員變量引用了對象B,則B是一個reachable object。從roots出發(fā)可以創(chuàng)建reachable objects graph,剩余對象即為unreachable,可以被回收。

指針修復(fù)是因為compact過程移動了heap對象,對象地址發(fā)生變化,需要修復(fù)所有引用指針,包括stack、CPU register中的指針以及heap中其他對象的引用指針。

Debug和release執(zhí)行模式之間稍有區(qū)別,release模式下后續(xù)代碼沒有引用的對象是unreachable的,而debug模式下需要等到當(dāng)前函數(shù)執(zhí)行完畢,這些對象才會成為unreachable,目的是為了調(diào)試時跟蹤局部對象的內(nèi)容
傳給了COM+的托管對象也會成為root,并且具有一個引用計數(shù)器以兼容COM+的內(nèi)存管理機制,引用計數(shù)器為0時這些對象才可能成為被回收對象。

Pinned objects指分配之后不能移動位置的對象,例如傳遞給非托管代碼的對象(或者使用了fixed關(guān)鍵字),GC在指針修復(fù)時無法修改非托管代碼中的引用指針,因此將這些對象移動將發(fā)生異常。pinned objects會導(dǎo)致heap出現(xiàn)碎片,但大部分情況來說傳給非托管代碼的對象應(yīng)當(dāng)在GC時能夠被回收掉

3. Generational 分代算法

程序可能使用幾百M、幾G的內(nèi)存,對這樣的內(nèi)存區(qū)域進行GC操作成本很高,分代算法具備一定統(tǒng)計學(xué)基礎(chǔ),對GC的性能改善效果比較明顯將對象按照生命周期分成新的、老的,根據(jù)統(tǒng)計分布規(guī)律所反映的結(jié)果,可以對新、老區(qū)域采用不同的回收策略和算法,加強對新區(qū)域的回收處理力度,爭取在較短時間間隔、較小的內(nèi)存區(qū)域內(nèi),以較低成本將執(zhí)行路徑上大量新近拋棄不再使用的局部對象及時回收掉分代算法的假設(shè)前提條件:

a). 大量新創(chuàng)建的對象生命周期都比較短,而較老的對象生命周期會更長

b). 對部分內(nèi)存進行回收比基于全部內(nèi)存的回收操作要快

c). 新創(chuàng)建的對象之間關(guān)聯(lián)程度通常較強。heap分配的對象是連續(xù)的,關(guān)聯(lián)度較強有利于提高CPU cache的命中率

.NET將heap分成3個代齡區(qū)域: Gen 0、Gen 1、Gen 2

Heap分為3個代齡區(qū)域,相應(yīng)的GC有3種方式: # Gen 0 collections, # Gen 1 collections, # Gen 2 collections。如果Gen 0 heap內(nèi)存達到閥值,則觸發(fā)0代GC,0代GC后Gen 0中幸存的對象進入Gen 1。如果Gen 1的內(nèi)存達到閥值,則進行1代GC,1代GC將Gen 0 heap和Gen 1 heap一起進行回收,幸存的對象進入Gen 2。2代GC將Gen 0 heap、Gen 1 heap和Gen 2 heap一起回收

Gen 0和Gen 1比較小,這兩個代齡加起來總是保持在16M左右;Gen 2的大小由應(yīng)用程序確定,可能達到幾G,因此0代和1代GC的成本非常低,2代GC稱為full GC,通常成本很高。粗略的計算0代和1代GC應(yīng)當(dāng)能在幾毫秒到幾十毫秒之間完成,Gen 2 heap比較大時full GC可能需要花費幾秒時間。大致上來講.NET應(yīng)用運行期間2代、1代和0代GC的頻率應(yīng)當(dāng)大致為1:10:100。

圖為一個ASP.NET程序運行的Performance Moniter,Gen 0 heap size(紅色)平均6M,Gen 1(藍色)平均5M,Gen 2(黃色)達到620M,Gen 0+Gen 1平均13.2M,最大19.8M

直觀上來看,程序的運行由一系列函數(shù)調(diào)用組成,函數(shù)運行期間會創(chuàng)建很多局部對象,函數(shù)結(jié)束之后也就產(chǎn)生大量待回收的對象。采用分代算法加強較新代齡的垃圾回收力度,通常能夠極大的提高垃圾回收效率,否則就是極特殊的程序,或者是不合理的對象關(guān)聯(lián)設(shè)計。例如ASP.NET程序,應(yīng)當(dāng)確保絕大部分用于HTTP 請求處理的對象在0代和1代垃圾回收中被釋放掉

為heap記錄幾個指針可以確定代齡區(qū)域范圍,創(chuàng)建reachable objects graph時根據(jù)對象的地址可以確定對象位于哪個代齡區(qū)域,0代GC在創(chuàng)建graph時如果遇到1代、2代heap對象,可以直接越過不用繼續(xù)遍歷下去,較老代齡的對象如果引用了較新代齡的對象,可以通過Win32 API GetWriteWatch訂閱內(nèi)存更新通知,記錄在"card table"中,輔助較低代齡的GC正確構(gòu)造graph

4. LOH

.NET 1.1和2.0中,85000字節(jié)以下的對象稱為小對象,分配在Gen 0 heap中,85000字節(jié)以上的對象稱為大對象,分配在Large Object Heap中,這是因為GC在heap壓縮時移動大的內(nèi)存塊需要消耗大量CPU時間,通過性能調(diào)優(yōu)實踐確定了85000字節(jié)這樣一個閥值。

LOH只在2代GC時進行回收,采用Mark-Sweep算法,沒有壓縮處理,因此LOH中的內(nèi)存分配是不連續(xù)的,使用一個空閑列表free list記錄LOH中的空閑空間,對釋放出來的空間進行管理。

上圖中obj1、obj2釋放之后,其空間合并起來成為free list的一個節(jié)點,隨后被分配給obj4

什么時候觸發(fā)垃圾回收?

前面已經(jīng)提到,0代和1代垃圾回收主要由閥值控制。初始時Gen 0 heap大小與CPU緩存的大小相關(guān),運行時CLR根據(jù)內(nèi)存請求狀態(tài)動態(tài)調(diào)整Gen 0 heap大小,但Gen 0和Gen 1總大小保持在16M左右

Gen 2 heap和LOH都在full GC時進行回收,full GC主要由2類事件觸發(fā):

a). 進入Gen 2 heap和LOH的對象很多,超過了一定比例。RegisterForFullGCNotification的參數(shù) maxGenerationThreshold、largeObjectHeapThreshold可以分別為Gen 2 heap和LOH設(shè)定這個值

b). 操作系統(tǒng)內(nèi)存吃緊的時候。CLR會接收到操作系統(tǒng)內(nèi)存緊張的通知消息,觸發(fā)full GC

5. Heap細節(jié)、擴容與收縮

Heap的代齡是邏輯上的結(jié)構(gòu),heap實際內(nèi)存申請和分配以及釋放以segment(段)為單位,workstation GC模式segment大小為16M,server GC模式segment大小為64M。Gen 0和Gen 1 heap總是位于同一個段中,叫做ephemeral segment(新生段),因此max(Gen 0 heap size+Gen 1 heap size)≈16M || 64M,Gen 2 heap由0個或多個segments組成,LOH由1個或多個segments組成。

.NET程序啟動時CLR為heap創(chuàng)建2個segment,一個作為ephemeral segment,另一個用于LOH。.NET使用VirtualAlloc申請和分配heap內(nèi)存,在LOH中分配新對象時沒有足夠的空間,或者1代GC 時進入Gen 2的對象過多空間不夠,.NET將為LOH或者小對象heap分配新的segment。申請新的segment失敗將由EE拋出OutOfMemory異常
Full GC后完全空閑的segments將被釋放掉,內(nèi)存返回給操作系統(tǒng)。

.NET 2.0對GC的一個重要改進是盡量改善heap碎片處理。heap碎片主要由pinned objects引起,改善措施主要有2個方面。首先是延遲升級,如果ephemeral segment存在pinned objects,則盡可能的延遲他們升級到Gen 2的時間點,考慮pinned objects的同時盡量充分利用當(dāng)前ephemeral segment的空間;其次是重復(fù)利用Gen 2的空間,如果Gen 2中存在pinned objects的segments釋放出了足夠空間,該segments可能重新作為ephemeral segment使用

6. GC方式

有Workstation GC with Concurrent GC off、 Workstation GC with Concurrent GC on、Server GC 3種

Workstation GC with Concurrent GC off: 用于單CPU機器實現(xiàn)高吞吐量,采用一系列策略觀察內(nèi)存分配以及每次GC的狀況,動態(tài)調(diào)整GC策略,盡可能使程序隨著運行時狀態(tài)的變化實現(xiàn)高效的GC操作,但進行GC時會凍結(jié)所有線程
Workstation GC with Concurrent GC on: 用于響應(yīng)時間非常重要的交互式程序,例如流媒體的播放等(如果一次full GC導(dǎo)致應(yīng)用程序中斷幾秒、十幾秒時間,用戶將無法忍受)。

這種方式利用多CPU對full GC進行并行處理,不是整個full GC期間凍結(jié)所有線程,而是將full GC切分成多次很短的時間對線程進行凍結(jié),在線程凍結(jié)時間之外,應(yīng)用程序仍然可以正常運行,進行內(nèi)存分配,這主要通過將Gen 0 heap size設(shè)置的比non-concurrent GC大很多而實現(xiàn),使得GC操作時線程仍然能夠在Gen 0 heap中進行內(nèi)存分配,但如果Gen 0 heap用完后GC仍然沒有結(jié)束,線程仍然會出現(xiàn)阻塞。這種方式付出的代價是working set和GC所需時間比non-concurrent GC要大一些。

Server GC: 用于多CPU機器的服務(wù)器應(yīng)用程序?qū)崿F(xiàn)高吞吐量和伸縮性,充分利用服務(wù)器的大內(nèi)存。.NET為每個CPU創(chuàng)建一組heap(包括Gen 0, 1, 2和LOH)和一個GC線程,每個CPU可以獨立的為相應(yīng)的heap執(zhí)行GC操作,而其他CPU則正常執(zhí)行處理。最佳的應(yīng)用場景是多線程之間內(nèi)存結(jié)構(gòu)基本相同,執(zhí)行的工作相同或類似

單CPU機器上只能使用workstation GC,默認情況下為Workstation GC with Concurrent GC on方式,單CPU機器上配置為Server GC無效,仍然使用workstation GC;多CPU服務(wù)器上的ASP.NET默認使用Server GC方式,Server GC時不能使用concurrent方式。

concurrent GC可以用于單CPU機器,它與CPU數(shù)量無關(guān)。

對于ASP.NET程序應(yīng)當(dāng)盡量保證一個CPU僅對應(yīng)一個GC線程,防止同一個CPU上面多個GC線程之間的沖突造成性能問題。如果使用了Web Garden則應(yīng)當(dāng)使用Workstation GC with Concurrent GC off。Web Garden為了提高吞吐量會導(dǎo)致多出幾倍的內(nèi)存使用,每個work process的內(nèi)存有很多重復(fù)部分,Web Garden的最佳應(yīng)用場景是多個進程之間使用一個共享的resource pool,避免內(nèi)存的重復(fù)并盡可能的提高吞吐量。在這一點上Server GC應(yīng)當(dāng)與Web Garden類似,但Web Garden在多個進程中,而Server GC是在同一個進程中通過多線程實現(xiàn),目前沒有發(fā)現(xiàn)Server GC方面深入一些的資料,很多東西只能根據(jù)現(xiàn)有資料做一些猜想為workstation GC禁用concurrent GC:

 
 
 
 
  1.  
  2.  
  3.  enabled="false"/> 
  4.  
  5.   

啟用Server GC:

 
 
 
 
  1.  
  2.  
  3.  enabled=“true"/> 
  4.  

詳解.NET內(nèi)存管理機制與垃圾回收就介紹到這里。

本文來自riccc的博客園文章《.NET內(nèi)存管理、垃圾回收》

【編輯推薦】

  1. .NET Framework詳解之內(nèi)存機制
  2. .NET內(nèi)存映射文件原理、創(chuàng)建及進程通訊
  3. .NET內(nèi)存管理的最佳實踐
  4. .NET 4.0內(nèi)存映射文件詳解
  5. .NET真的不用管內(nèi)存嗎?從List﹤T﹥列表聊起

網(wǎng)頁名稱:詳解.NET內(nèi)存管理機制與垃圾回收
URL網(wǎng)址:http://www.dlmjj.cn/article/dpjsghe.html