天天看點

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:壓縮。當垃圾被回收之後,就會出現磁盤碎片,那麼就要對托管堆進行整理,即壓縮。将沒有被回收的對象放在一起,靠近托管堆開始的位置,将剩餘的記憶體騰出空間來以便存放新的對象。由于壓縮很多對象就會移動位置,而引用他們的指針都會變得無效,是以托管堆要修改所有指針的指向,以保證不會因為垃圾回收而讓對象變得不可到達,指針變得無效。

更多關于記憶體管理和垃圾回收的内容,請等待我的下一篇部落格。

本文轉自啊漢部落格園部落格,原文連結:http://www.cnblogs.com/hlxs/archive/2012/04/12/2443722.html