C#/.Net 的托管堆和垃圾回收
1、托管堆基礎(chǔ)
資源包括包括:文件、內(nèi)存緩沖區(qū)、網(wǎng)絡(luò)連接等。
以下是訪問(wèn)一個(gè)資源所需的步驟:
2、托管堆分配資源
CLR要求所有對(duì)象都從托管堆分配。進(jìn)程初始化時(shí),CLR劃出一個(gè)地址空間區(qū)域作為托管堆,CLR還要維護(hù)一個(gè)指針,我把它稱作NextObjPtr。該指針指向下一個(gè)對(duì)象在堆中的分配位置。
????????你的應(yīng)用程序的內(nèi)存受進(jìn)程的虛擬地址空間的限制。32位進(jìn)程最多能分配1.5GB,64位進(jìn)程最多能分配8TB。
C#的new操作符導(dǎo)致CLR執(zhí)行以下步驟:
1. 計(jì)算類型的字段(以及從基類型繼承的字段)所需的字節(jié)數(shù)。
2. 加上對(duì)象的開銷所需的字節(jié)數(shù)。每個(gè)對(duì)象都有兩個(gè)開銷字段:類型對(duì)象指針和同步塊索引。對(duì)于32位應(yīng)用程序,這兩個(gè)字段各自需要32位,所以每個(gè)對(duì)象都要8字節(jié)。對(duì)于64位應(yīng)用程序,這兩個(gè)字段各自需要64位,所以每個(gè)對(duì)象要增加16個(gè)字節(jié)。
3. CLR檢查區(qū)域中是否有分配對(duì)象所需的字節(jié)數(shù)。如果托管堆有足夠的可用空間,就在NetxObjPtr指針指向的地址處放入對(duì)象,為對(duì)象分配的字節(jié)會(huì)被清零。接著調(diào)用類型的構(gòu)造器(為this參數(shù)傳遞NextObjPtr),new操作符返回對(duì)象引用。就在返回這個(gè)對(duì)象引用之前,NextObjPtr指針的值會(huì)加上這個(gè)對(duì)象占用的字節(jié)數(shù)來(lái)得到一個(gè)新值,即下個(gè)對(duì)象放入托管堆時(shí)的地址。
3、垃圾回收算法
?應(yīng)用程序調(diào)用new操作符創(chuàng)建對(duì)象時(shí),可能沒(méi)有足夠地址空間來(lái)分配該對(duì)象,發(fā)現(xiàn)空間不夠,CLR就執(zhí)行垃圾回收。
????????至于對(duì)象生存期的管理,有的系統(tǒng)采用的是某種引用計(jì)數(shù)算法。在這種系統(tǒng)中,堆上的每個(gè)對(duì)象都維護(hù)著一個(gè)內(nèi)存字段來(lái)統(tǒng)計(jì)程序中多少“部分”正在使用對(duì)象。隨著每一“部分”到達(dá)代碼中某個(gè)不再需要對(duì)象的地方,就遞減對(duì)象的計(jì)算字段。計(jì)數(shù)字段成0,對(duì)象就可以從內(nèi)存中刪除了。許多引用計(jì)數(shù)系統(tǒng)最大的問(wèn)題是處理不好循環(huán)引用。
?????????????鑒于引用計(jì)數(shù)垃圾回收器算法存在的問(wèn)題,CLR改為使用一種引用跟蹤算法。引用跟蹤算法中關(guān)心引用類型的變量,因?yàn)橹挥羞@種變量才能引用堆上的對(duì)象;值類型變量直接包含值類型實(shí)例。引用類型變量可在許多場(chǎng)合使用,包括靜態(tài)和實(shí)例字段,或者方法的參數(shù)和局部變量。我們將所有引用類型的變量都稱為根。
???????????1、CLR開始GC時(shí),首先暫停進(jìn)程中的所有線程。這樣可以防止線程在CLR檢查期間訪問(wèn)對(duì)象并更改其狀態(tài)。?????
???????????2、然后,CLR進(jìn)入GC的標(biāo)記階段。在這個(gè)階段,CLR遍歷堆中的所有對(duì)象,將同步塊索引字段中的一位設(shè)為0。這表明所有對(duì)象都應(yīng)刪除。???
???????????3、然后,CLR檢查所有的活動(dòng)根,查看它們引用了哪些對(duì)象。這正是CLR的GC稱為引用跟蹤GC的原因。如果一個(gè)根包含NULL,CLR忽略這個(gè)根并繼續(xù)檢查下一個(gè)根。
???????????4、任何根如果引用了堆上的對(duì)象,CLR都會(huì)標(biāo)記那個(gè)對(duì)象,也就是將該對(duì)象的同步塊索引中的位設(shè)為1, 標(biāo)記過(guò)程會(huì)持續(xù),直至應(yīng)用程序的所有跟所有檢查完畢。???????
???????????5、檢查完畢后,堆中的對(duì)象要么已標(biāo)記,要么未標(biāo)記。已標(biāo)記的對(duì)象不能被垃圾回收,因?yàn)橹辽儆幸粋€(gè)根在引用它。我們說(shuō)這種對(duì)象是可達(dá)的。因?yàn)閼?yīng)用程序代碼可通過(guò)仍在引用它的變量抵達(dá)(訪問(wèn))它。未標(biāo)記的對(duì)象是不可達(dá)的。因?yàn)閼?yīng)用程序中不存在使對(duì)象能被再次訪問(wèn)的根。
?????????????6、CLR知道哪些對(duì)象可以幸存,哪些可以刪除后,就進(jìn)入GC的壓縮(不是那個(gè)壓縮,類似于碎片整理)階段。在這個(gè)階段。CLR對(duì)堆中標(biāo)記的對(duì)象進(jìn)行“乾坤大挪移”。
????????????7、在內(nèi)存中移動(dòng)了對(duì)象之后有一個(gè)問(wèn)題亟待解決。引用幸存對(duì)象的根現(xiàn)在引用的還是對(duì)象最初在內(nèi)存中的位置,而非移動(dòng)后的位置。被暫停的線程恢復(fù)執(zhí)行時(shí),將訪問(wèn)舊的內(nèi)存位置,會(huì)造成內(nèi)存損壞。這顯然是不能容忍的,所以作為壓縮階段的一部分,CLR還要從每個(gè)根減去所引用對(duì)象在內(nèi)存中偏移的字節(jié)數(shù)。這樣就能保證每個(gè)根還是引用和之前一樣的對(duì)象,只是對(duì)象在內(nèi)存中變換了位置。
????????????8、壓縮階段完成后,CLR恢復(fù)應(yīng)用程序的所有線程。
重要提示:???靜態(tài)字段引用的對(duì)象一直存在,直到用于加載類型的AppDomain卸載為止。內(nèi)存泄漏的一個(gè)常見原因就是讓靜態(tài)字段引用某個(gè)集合對(duì)象,然后不停地往集合添加數(shù)據(jù)項(xiàng)。靜態(tài)字段使集合對(duì)象一直存活,而集合對(duì)象使所有數(shù)據(jù)項(xiàng)一直存活。因此應(yīng)該盡量避免使用靜態(tài)字段。(或者參照前面的玩法,當(dāng)我們不用靜態(tài)變量的時(shí)候,可以立馬置為null,那么垃圾就會(huì)被回收)。
4、代:提升性能
CLR的GC是基于代的垃圾回收器。它對(duì)代碼做了如下假設(shè):
- 對(duì)象越新,生存期越短
- 對(duì)象越老,生存期越長(zhǎng)
- 回收堆的一部分,速度快于回收整個(gè)堆
第一個(gè)假設(shè)是越新的對(duì)象活的越短。因此,第0代包含跟多垃圾的可能性很大,能回收更多的內(nèi)存。由于忽略了第1代中的對(duì)象,所以加快了垃圾回收速度。
第二個(gè)假設(shè)越老的對(duì)象活的越長(zhǎng)。也就是說(shuō),第1代對(duì)象在應(yīng)用程序中很有可能繼續(xù)可達(dá)(沒(méi)被回收)的。如果垃圾回收器檢查第1代中的對(duì)象,很有可能找不到多少垃圾。
由于第0代已滿,所以必須開始垃圾回收。但這一次垃圾回收器發(fā)現(xiàn)第1代占用了太多內(nèi)存,以至于用完了預(yù)算。 由于前幾次對(duì)第0代進(jìn)行回收時(shí),第1代可能已經(jīng)有許多對(duì)象變得不可達(dá)(該回收)。所以這次垃圾回收器決定檢查第1代和第0代的所有對(duì)象。 兩代都被垃圾回收后,??就出現(xiàn)了第2代了。 空的是0代, 0代幸存者變?yōu)?代,1代幸存者變?yōu)?代。
???????????托管堆只支持三代:第0代, 第1代,第2代。
CLR 的垃圾回收器是自動(dòng)調(diào)節(jié)的:
1、如果垃圾回收器發(fā)現(xiàn)在回收第0代后存活下來(lái)的對(duì)象很少,就可能減少第0代的預(yù)算。已分配空間的減少意味著垃圾回收將更頻繁地發(fā)生。
2、另一方面,如果垃圾回收器回收了第0代,發(fā)現(xiàn)還有很多對(duì)象存活,沒(méi)有多少內(nèi)存被回收就會(huì)增加第0代的預(yù)算。
3、垃圾回收器用類似的??啟發(fā)式算法?調(diào)整第1代 和 第2代的預(yù)算。
5、垃圾回收觸發(fā)條件
1、CLR在檢測(cè)第0代超過(guò)預(yù)算時(shí)會(huì)觸發(fā)一次GC,這是GC最常見的觸發(fā)條件,還有其它的觸發(fā)如下:
2、代碼顯示調(diào)用System.GC的靜態(tài)Collect方法,??大多時(shí)候都要避免調(diào)用這個(gè)方法;最好讓垃圾回收器自行斟酌執(zhí)行,讓它根據(jù)應(yīng)用程序的行為調(diào)整各個(gè)代的預(yù)算。
3、Windows報(bào)告低內(nèi)存情況
4、CLR正在卸載AppDomain
5、CLR正在關(guān)閉
?
官方的介紹:(良心發(fā)現(xiàn) 有中文)
??????????https://docs.microsoft.com/zh-cn/dotnet/articles/standard/garbagecollection/
6、 擴(kuò)展閱讀
unity在線文檔Understanding the managed heap
https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity4-1.html
Optimizing garbage collection in Unity games
https://unity3d.com/cn/learn/tutorials/topics/performance-optimization/optimizing-garbage-collection-unity-games?playlist=44069
C# Garbage Collection Tutorial
https://stackify.com/c-garbage-collection/
Memory Management/Stacks and Heaps
https://en.wikibooks.org/wiki/Memory_Management/Stacks_and_Heaps
Lambda Expressions vs. Anonymous Methods
https://blogs.msdn.microsoft.com/ericlippert/2007/01/10/lambda-expressions-vs-anonymous-methods-part-one/
推薦書籍:
《垃圾回收的算法與實(shí)現(xiàn)》
《C#/.Net 的托管堆和垃圾回收》
《CLR via C#》
?
轉(zhuǎn)載自:https://blog.csdn.net/u010019717/article/details/66975553
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/u010019717/article/details/66975553
總結(jié)
以上是生活随笔為你收集整理的C#/.Net 的托管堆和垃圾回收的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 原生JS写Ajax的请求函数
- 下一篇: html 调用c#dll中的控件,C#调