C#內存管理與垃圾回收


垃圾回收還得從根說起,就像生兒育女一樣。

:根是一個位置,存放一個指針,該指針指向托管堆中的一個對象,或是一個空指針不指向任何對象,即為null。根存在線程棧或托管堆中,大部分的跟都在線程棧上,因為定義的變量就存在線程棧上,類型對象指針存在托管堆中,因為實例化一個對象要額外分配兩個字段“類型對象指針”和“同步塊索引”。

 

類型對象指針的作用。實例化一個對象並沒有為其方法分配內存,類型的靜態字段分配內存,而實例要向調用屬於類型的一些東西,就必須通過類型對象指針。如對象的實例是共用類型的方法,實例只需要通過類型對象指針調用類型的方法,更多關於方法的調用請看我的這篇博客

 

同步塊索引的作用。1:用於lock,使對象在同一時刻只能一個線程訪問;2:用於獲取對象的hashCode;3:在垃圾回收時標志某個對象是否是垃圾。關於lock最經典的一個例子就是單例了,大家的實現都是實例化一個object對象,然后鎖住它,然后在判斷是否要實例要實現單例的那個對象。我們為什么要實例化一個object,而不是直接lock(typeof(object)),那是因為這樣會把object這個類型給鎖住,鎖住期間,任何使用線程使用lock(typeof(object))就必須等待,object還是可以正常使用。lock能起到單線程訪問的原因是:它里面有一個空的for死循環,一直在讀同步塊索引中的一個位,如果這個位沒有被標志跳出循環,如果被標志就一直執行循環,直到方法執行完成,其他線程就一直等待,現在你知道lock能使你的程序只能單線程反問也知道lock的效率低了吧。

 

NextObjPtr一個最牛B的指針。CLR中的所有資源都從托管堆中分配,托管堆是一塊連續的內存空間,維護一個指針NextObjPtr,它指向上一個對象地址的后面,下一個對象的開始位置,若托管堆中沒有對象就指向托管堆的開始位置,每分配一個對象就將NextObjPtr指向這個對象的后面,以准備開始分配下一個對象。NextObjptr指針移動的位置其實就是上一個對象所在空間的長度,從指向對象的開始位置改為對象的末尾嗎。從哪里開始分配對象就全靠NextObjPtr啦。

 

實例化一個對象需要多少空間?對象的所有字段所需的內存+類型對象指針+同步塊索引。關於類型對象指針和同步塊索引的作用前面已經提過了。有些字段沒有明顯定義,但它確確實實存在,每個對象除了object的對象都有base字段,通過它可以調用父類的實例字段和方法,通過它你可以訪問你爺爺的爺爺定義的字段和方法。CLR用遞歸的方式調用父類的方法,當然也要看,你爺爺是否願意讓你調用,原因你懂的。

 

在垃圾回收開始之前速度比C。對象就這樣開心的在托管堆中分配,托管堆的容量是有限的,總有一天第0代會滿,容不下一粒沙子。垃圾回收就出場了,在垃圾回收出場之前,你使用內存很happy,當然速度是非常快,比C語言的速度還快,因為C的內存是隨便分配,只要找到合適大小的區域,就在那里分配內存了,這樣會導致內存碎片,有時需要一塊大的內存,需要遍歷多處。垃圾回收的時候日子就不是那么好過了。速度肯定比C慢了,看下面你就知道垃圾回收的時候,程序的速度為什么慢了。

 

垃圾回收分兩步:1:標記;2:壓縮

1:標記。在垃圾回收開始的時候,垃圾回收器視托管堆中的所有對象都為垃圾,即線程棧上沒有指針指向托管堆。這樣的估計是因為一個對象被視為垃圾就是它沒有被引用,當垃圾回收開始的時候,垃圾回收器會沿着線程棧線性掃描,當線程棧上的一個變量引用了托管堆中的對象時,垃圾回收器就會將這個對象標記,即修改該對象同步塊索引中的一個特定的位,同步塊索引就是一個bit數組,每一個元素都有它特定的作用,上面就列出了我所知道的三個功能。被標記的對象也可能引用其他的對象,而被引用的對象同樣會被標記,垃圾回收器是用遞歸的方式將這些對象一一標記的,一個對象可能會被多個對象引用,當垃圾回收器發現某個對象被標記時就會退出遞歸,因為再往下遞歸完全是多余,而且還可能出現死循環。

垃圾回收器就這樣線性的掃描線程棧,遞歸的掃描托管堆,最后將托管堆中所有被引用的對象標記,而沒有被標記的對象就是垃圾,等着被回收。

 

2:壓縮。當垃圾被回收之后,就會出現磁盤碎片,那么就要對托管堆進行整理,即壓縮。將沒有被回收的對象放在一起,靠近托管堆開始的位置,將剩余的內存騰出空間來以便存放新的對象。由於壓縮很多對象就會移動位置,而引用他們的指針都會變得無效,所以托管堆要修改所有指針的指向,以保證不會因為垃圾回收而讓對象變得不可到達,指針變得無效。

壓縮完了之后,又騰出了空間,又可以分配新的對象,當第0代滿了之后又進行垃圾回收,垃圾回收就這樣一直進行着,直到回收了3代還是沒有內存可以分配,那就是彈盡糧絕的時候了,CLR會告訴你OutOfMemoryException。CLR的內存被的程序吃光了。更多關於代的信息,可以看我的這篇博客。在第0代滿的時候就會進行垃圾回收,第0代回收完之后還是沒有足夠的內存存放當前對象就回收第1代,如果還是不夠就回收第2代,夠就不回收下一代,垃圾回收還可以用代碼控制GC.Collect()。

更多關於內存管理和垃圾回收的內容,請等待我的下一篇博客。

 

作者:陳太漢

博客:http://www.cnblogs.com/hlxs/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM