垃圾收集器是一種動態存儲分配器,它自動釋放程序再也不須要的已分配的塊,這些塊也稱爲垃圾。在程序員看來,垃圾就是再也不被引用的對象。自動回收垃圾的過程則稱爲垃圾收集(garbage collection)。在一個支持垃圾收集的語言中,程序顯式地申請內存,但從不須要顯式的釋放它們。垃圾收集器會按期識別垃圾塊,並將垃圾塊放回空閒鏈表中。顯然,C語言的malloc包不是一個帶GC功能的分配器,程序員顯式 調用malloc分配內存,也須要顯式調用free釋放它。而像java、C#這些語言等則提供了垃圾收集器。這篇文章的內容爲介紹一些經常使用的GC算法,同時簡單提一下C++的GC機制。html
垃圾收集器將存儲器視爲一張有向可達圖。圖中的節點能夠分爲兩組:一組稱爲根節點,對應於不在堆中的位置,這些位置能夠是寄存器、棧中的變量,或者是虛擬存儲器中讀寫數據區域的全局變量;另一組稱爲堆節點,對應於堆中一個分配塊,以下圖:
java
當存在一個根節點可到達某個堆節點時,咱們稱該堆節點是可達的,反之稱爲不可達。不可達堆節點爲垃圾。可見垃圾收集的目標便是從從根集出發,尋找未被引用的堆節點,並將其釋放。程序員
垃圾收集算法是一個重要而活躍的研究領域,自從20世紀60年代開始對垃圾收集進行研究以來,垃圾算法的研究從未中止。常見的垃圾收集算法有一下這幾種類型:算法
引用技術算法是惟一一種不用用到根集概念的GC算法。其基本思路是爲每一個對象加一個計數器,計數器記錄的是全部指向該對象的引用數量。每次有一個新的引用指向這個對象時,計數器加一;反之,若是指向該對象的引用被置空或指向其它對象,則計數器減一。當計數器的值爲0時,則自動刪除這個對象。這個思路能夠參考C++ 引用計數技術及智能指針的簡單實現。併發
引用計數算法的優勢是實現簡單,在原生不支持GC的語言中也能容易實現出來。另外一個優勢這種垃圾收集機制是即時回收,也便是對象再也不被引用的瞬間就當即被釋放掉。而其缺點是若存在對象的循環引用,沒法釋放這些對象,例圖:
函數
缺點二是多個線程同時對引用計數進行增減時,引用計數的值可能會產生不一致的問題,必須使用併發控制機制解決這一問題,也是一個不小的開銷。.net
這個算法也稱爲標記清除算法,爲McCarthy首創。它也是目前公認的最有效的GC方案。Mark&Sweep垃圾收集器由標記階段和回收階段組成,標記階段標記出根節點全部可達的對節點,清除階段釋放每一個未被標記的已分配塊。典型地,塊頭部中空閒的低位中的一位用來表示這個塊是否已經被標記了。經過Mark&Sweep算法動態申請內存時,先按需分配內存,當內存不足以分配時,從寄存器或者程序棧上的引用出發,遍歷上述的有向可達圖並做標記(標記階段),而後再遍歷一次內存空間,把全部沒有標記的對象釋放(清除階段)。所以在收集垃圾時須要中斷正常程序,在程序涉及內存大、對象多的時候中斷過程可能有點長。固然,收集器也能夠做爲一個獨立線程不斷地定時更新可達圖和回收垃圾。該算法不像引用計數可對內存進行即時回收,可是它解決了引用計數的循環引用問題,所以有的語言把引用計數算法搭配Mark & Sweep 算法構成GC機制。
線程
Mark & Sweep算法的缺點是在分配大量對象時,且對象大都須要回收時,回收中斷過程可能消耗很大。而節點複製算法則恰好相反,當須要回收的對象越多時,它的開銷很小,而當大部分對象都不須要回收時,其開銷反而很大。
算法的基本思路是這樣的:從根節點開始,被引用的對象都會被複制到一個新的存儲區域中,而剩下的對象則是再也不被引用的,即爲垃圾,留在原來的存儲區域。釋放內存時,直接把原來的存儲區域釋放掉,繼續維護新的存儲區域便可。過程如圖:指針
能夠看到,當被引用對象(非垃圾對象)不少時,須要複製不少的對象到新存儲區域。htm
以上三種基本算法各有各的優缺點,也各自有許多改進的方案。經過對這三種方式的融合,出現了一些更加高級的方式。而高級GC技術中最重要的一種爲分代回收。它的基本思路是這樣的:程序中存在大量的這樣的對象,它們被分配出來以後很快就會被釋放,但若是一個對象分配後至關長的一段時間內都沒有被回收,那麼極有可能它的生命週期很長,嘗試收集它是無用功。爲了讓GC變得更高效,咱們應該對剛誕生不久的對象進行重點掃描,這樣就能夠回收大部分的垃圾。爲了達到這個目的,咱們須要依據對象的」年齡「進行分代,剛剛生成不久的對象劃分爲新生代,而存在時間長的對象劃分爲老生代,根據實現方式的不一樣,能夠劃分爲多個代。
一種回收的實現策略能夠是:首先從根開始進行一次常規掃描,掃描過程當中若是遇到老生代對象則不進行遞歸掃描,這樣可大大減小掃描次數。這個過程可以使用標記清除算法或者複製收集算法。而後,把掃描後殘留下來的對象劃分到老生代,如果採用標記清除算法,則應該在對象上設置某個標誌位標誌其年齡;如果採用複製收集,則只須要把新的存儲區域內對象設置爲老生代就能夠了。而實際的實現上,分代回收算法的方案五花八門,經常會融合幾種基本算法。
而其餘的改進算法數量很是龐大,但大都基於上述的三種基本算法。
C語言自己沒有提供GC機制,而C++ 0x則提供了基於引用計數算法的智能指針進行內存管理。也有一些不做爲C++標準的垃圾回收庫,如著名的Boehm庫。藉助其餘的算法也能夠實現C/C++的GC機制,如前面所說的標記清除算法。
當應用程序使用malloc試圖從堆上得到內存塊時,一般都是以常規方式來調用malloc,而當malloc找不到合適空閒塊的時候,它就會去調用垃圾收集器,以回收垃圾到空閒鏈表。此時,垃圾收集器將識別出垃圾塊,並經過free函數將它們返回給堆。這樣看來,垃圾收集器代替咱們調用了free函數,從而讓咱們顯式分配,而無須顯式釋放。
上圖中的垃圾收集器爲一個保守的垃圾收集器。保守的定義是:每一個可達的塊都可以正確地被標記爲可達,而一些不可達塊卻可能被錯誤地標記爲可達。其根本緣由在於C/C++語言不會用任何類型信息來標記存儲器的位置,即對於一個整數類型來講,語言自己沒有一種顯式的方法來判斷它是一個整數仍是一個指針。所以,若是某個整數值所表明的地址剛好的某個不可達塊中某個字的地址,那麼這個不可達塊就會被標記爲可達。因此,C/C++所實現的垃圾收集器都不是精確的,存在着回收不乾淨的現象。而像JAVA的垃圾收集器則是精確回收。在《關於C++ 0x 裏垃圾收集器的講座》這篇文章裏提到,C++標準提案中使用gc_strict、 gc_relax這樣的關鍵字來描述一個內存區內有沒有指針,但沒法精確到每一個數據上。實際上,早在07年,一份C++標準提案N2670就提出要將垃圾回收機制做爲加入C++,最後提案是沒有經過,其緣由大概是由於實現複雜,因爲語言自己緣由存在這樣那樣的限制。因此在C++ 0x中除了shard_ptr、weak_ptr這些智能指針外,咱們並沒看看到GC機制的身影。而至於C++是如何解決引用計數的循環引用問題以及併發控制問題,咱們將以另一篇文章進行介紹。
深刻理解計算機系統 [美]Randal E.Bryant / David O'Hallaron 機械工業出版社
代碼的將來 [日] 松本行弘 人民郵電出版社
C++標準提案N2670 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2670.htm
關於C++ 0x 裏垃圾收集器的講座 http://blog.csdn.net/g9yuayon/article/details/1702694