C#/.Net 的托管堆和垃圾回收
1、托管堆基礎
資源包括包括:文件、內存緩沖區、網絡連接等。
以下是訪問一個資源所需的步驟:
2、托管堆分配資源
CLR要求所有對象都從托管堆分配。進程初始化時,CLR劃出一個地址空間區域作為托管堆,CLR還要維護一個指針,我把它稱作NextObjPtr。該指針指向下一個對象在堆中的分配位置。
????????你的應用程序的內存受進程的虛擬地址空間的限制。32位進程最多能分配1.5GB,64位進程最多能分配8TB。
C#的new操作符導致CLR執行以下步驟:
1. 計算類型的字段(以及從基類型繼承的字段)所需的字節數。
2. 加上對象的開銷所需的字節數。每個對象都有兩個開銷字段:類型對象指針和同步塊索引。對于32位應用程序,這兩個字段各自需要32位,所以每個對象都要8字節。對于64位應用程序,這兩個字段各自需要64位,所以每個對象要增加16個字節。
3. CLR檢查區域中是否有分配對象所需的字節數。如果托管堆有足夠的可用空間,就在NetxObjPtr指針指向的地址處放入對象,為對象分配的字節會被清零。接著調用類型的構造器(為this參數傳遞NextObjPtr),new操作符返回對象引用。就在返回這個對象引用之前,NextObjPtr指針的值會加上這個對象占用的字節數來得到一個新值,即下個對象放入托管堆時的地址。
3、垃圾回收算法
?應用程序調用new操作符創建對象時,可能沒有足夠地址空間來分配該對象,發現空間不夠,CLR就執行垃圾回收。
????????至于對象生存期的管理,有的系統采用的是某種引用計數算法。在這種系統中,堆上的每個對象都維護著一個內存字段來統計程序中多少“部分”正在使用對象。隨著每一“部分”到達代碼中某個不再需要對象的地方,就遞減對象的計算字段。計數字段成0,對象就可以從內存中刪除了。許多引用計數系統最大的問題是處理不好循環引用。
?????????????鑒于引用計數垃圾回收器算法存在的問題,CLR改為使用一種引用跟蹤算法。引用跟蹤算法中關心引用類型的變量,因為只有這種變量才能引用堆上的對象;值類型變量直接包含值類型實例。引用類型變量可在許多場合使用,包括靜態和實例字段,或者方法的參數和局部變量。我們將所有引用類型的變量都稱為根。
???????????1、CLR開始GC時,首先暫停進程中的所有線程。這樣可以防止線程在CLR檢查期間訪問對象并更改其狀態。?????
???????????2、然后,CLR進入GC的標記階段。在這個階段,CLR遍歷堆中的所有對象,將同步塊索引字段中的一位設為0。這表明所有對象都應刪除。???
???????????3、然后,CLR檢查所有的活動根,查看它們引用了哪些對象。這正是CLR的GC稱為引用跟蹤GC的原因。如果一個根包含NULL,CLR忽略這個根并繼續檢查下一個根。
???????????4、任何根如果引用了堆上的對象,CLR都會標記那個對象,也就是將該對象的同步塊索引中的位設為1, 標記過程會持續,直至應用程序的所有跟所有檢查完畢。???????
???????????5、檢查完畢后,堆中的對象要么已標記,要么未標記。已標記的對象不能被垃圾回收,因為至少有一個根在引用它。我們說這種對象是可達的。因為應用程序代碼可通過仍在引用它的變量抵達(訪問)它。未標記的對象是不可達的。因為應用程序中不存在使對象能被再次訪問的根。
?????????????6、CLR知道哪些對象可以幸存,哪些可以刪除后,就進入GC的壓縮(不是那個壓縮,類似于碎片整理)階段。在這個階段。CLR對堆中標記的對象進行“乾坤大挪移”。
????????????7、在內存中移動了對象之后有一個問題亟待解決。引用幸存對象的根現在引用的還是對象最初在內存中的位置,而非移動后的位置。被暫停的線程恢復執行時,將訪問舊的內存位置,會造成內存損壞。這顯然是不能容忍的,所以作為壓縮階段的一部分,CLR還要從每個根減去所引用對象在內存中偏移的字節數。這樣就能保證每個根還是引用和之前一樣的對象,只是對象在內存中變換了位置。
????????????8、壓縮階段完成后,CLR恢復應用程序的所有線程。
重要提示:???靜態字段引用的對象一直存在,直到用于加載類型的AppDomain卸載為止。內存泄漏的一個常見原因就是讓靜態字段引用某個集合對象,然后不停地往集合添加數據項。靜態字段使集合對象一直存活,而集合對象使所有數據項一直存活。因此應該盡量避免使用靜態字段。(或者參照前面的玩法,當我們不用靜態變量的時候,可以立馬置為null,那么垃圾就會被回收)。
4、代:提升性能
CLR的GC是基于代的垃圾回收器。它對代碼做了如下假設:
- 對象越新,生存期越短
- 對象越老,生存期越長
- 回收堆的一部分,速度快于回收整個堆
第一個假設是越新的對象活的越短。因此,第0代包含跟多垃圾的可能性很大,能回收更多的內存。由于忽略了第1代中的對象,所以加快了垃圾回收速度。
第二個假設越老的對象活的越長。也就是說,第1代對象在應用程序中很有可能繼續可達(沒被回收)的。如果垃圾回收器檢查第1代中的對象,很有可能找不到多少垃圾。
由于第0代已滿,所以必須開始垃圾回收。但這一次垃圾回收器發現第1代占用了太多內存,以至于用完了預算。 由于前幾次對第0代進行回收時,第1代可能已經有許多對象變得不可達(該回收)。所以這次垃圾回收器決定檢查第1代和第0代的所有對象。 兩代都被垃圾回收后,??就出現了第2代了。 空的是0代, 0代幸存者變為1代,1代幸存者變為2代。
???????????托管堆只支持三代:第0代, 第1代,第2代。
CLR 的垃圾回收器是自動調節的:
1、如果垃圾回收器發現在回收第0代后存活下來的對象很少,就可能減少第0代的預算。已分配空間的減少意味著垃圾回收將更頻繁地發生。
2、另一方面,如果垃圾回收器回收了第0代,發現還有很多對象存活,沒有多少內存被回收就會增加第0代的預算。
3、垃圾回收器用類似的??啟發式算法?調整第1代 和 第2代的預算。
5、垃圾回收觸發條件
1、CLR在檢測第0代超過預算時會觸發一次GC,這是GC最常見的觸發條件,還有其它的觸發如下:
2、代碼顯示調用System.GC的靜態Collect方法,??大多時候都要避免調用這個方法;最好讓垃圾回收器自行斟酌執行,讓它根據應用程序的行為調整各個代的預算。
3、Windows報告低內存情況
4、CLR正在卸載AppDomain
5、CLR正在關閉
?
官方的介紹:(良心發現 有中文)
??????????https://docs.microsoft.com/zh-cn/dotnet/articles/standard/garbagecollection/
6、 擴展閱讀
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/
推薦書籍:
《垃圾回收的算法與實現》
《C#/.Net 的托管堆和垃圾回收》
《CLR via C#》
?
轉載自:https://blog.csdn.net/u010019717/article/details/66975553
版權聲明:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/u010019717/article/details/66975553
總結
以上是生活随笔為你收集整理的C#/.Net 的托管堆和垃圾回收的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 原生JS写Ajax的请求函数
- 下一篇: WEB文件上传之JQuery ajaxf