1.引用計數算法web
引用計數(Reference Counting)算法是每一個對象計算指向它的指針的數量,當有一個指針指向本身時計數值加1;當刪除一個指向本身的指針時,計數值減1,若是計數值減爲0,說明已經不存在指向該對象的指針了,因此它能夠被安全的銷燬了。能夠很直觀的用下面的圖表示:算法
引用計數算法的優勢在於內存管理的開銷分佈於整個應用程序運行期間,很是的「平滑」,無需掛起應用程序的運行來作垃圾回收;而它的另一個優點在於空間上的引用局部性比較好,當某個對象的引用計數值變爲0時,系統無需訪問位於堆中其餘頁面的單元,然後面咱們將要看到的幾種垃圾回收算法在回收前都回遍歷全部的存活單元,這可能會引發換頁(Paging)操做;最後引用計數算法提供了一種相似於棧分配的方式,廢棄即回收,後面咱們將要看到的幾種垃圾回收算法在對象廢棄後,都會存活一段時間,纔會被回收。編程
引用計數算法有着諸多的優勢,但它的缺點也是很明顯的。首先能看到的一點是時間上的開銷,每次在對象建立或者釋放時,都要計算引用計數值,這會引發一些額外的開銷;第二是空間上的開銷,因爲每一個對象要保持本身被引用的數量,必須付出額外的空間來存放引用計數值;引用計數算法最大的缺點就在於它沒法處理環形引用,以下圖所示:安全
此 處藍色的這兩個對象既不可達也沒法回收,由於彼此之間互相引用,它們各自的計數值都不爲0,這種狀況對引用計數算法來講是無能爲力的,而其餘的垃圾回收算法卻能很好的處理環形引用。app
引用計數算法最著名的運用,莫過於微軟的COM技術,大名鼎鼎的IUnknown接口:編程語言
其中的AddRef和Release就是用來讓組件本身管理其生命週期,而客戶程序只關心接口,而無須再去關心組件的生命週期,一個簡單的使用示例以下:ide
上面的客戶程序在CreateInstance中已經調用過AddRef,因此無需再次調用,而在使用完接口後調用Release,這樣組件本身維護的計數值將會改變。下面代碼給出一個簡單的實現AddRef和Release示例:函數
在編程語言Python中,使用也是引用計數算法,當對象的引用計數值爲0時,將會調用__del__函數,至於爲何Python要選用引用計數算法,據我看過的一篇文章裏面說,因爲Python做爲腳本語言,常常要與C/C++這些語言交互,而使用引用計數算法能夠避免改變對象在內存中的位置,而Python爲了解決環形引用問題,也引入gc模塊,因此本質上Python的GC的方案是混合引用計數和跟蹤(後面要講的三個算法)兩種垃圾回收機制。oop
2.標記-清除算法性能
標記-清除(Mark-Sweep)算法依賴於對全部存活對象進行一次全局遍從來肯定哪些對象能夠回收,遍歷的過程從根出發,找到全部可達對象,除此以外,其它不可達的對象就是垃圾對象,可被回收。整個過程分爲兩個階段:標記階段找到全部存活對象;清除階段清除全部垃圾對象。
標記階段
清除階段
相比較引用計數算法,標記-清除算法能夠很是天然的處理環形引用問題,另外在建立對象和銷燬對象時時少了操做引用計數值的開銷。它的缺點在於標記-清除算法是一種「中止-啓動」算法,在垃圾回收器運行過程當中,應用程序必須暫時中止,因此對於標記-清除算法的研究如何減小它的停頓時間,而分代式垃圾收集器就是爲了減小它的停頓時間,後面會說到。另外,標記-清除算法在標記階段須要遍歷全部的存活對象,會形成必定的開銷,在清除階段,清除垃圾對象後會形成大量的內存碎片。
3.標記-縮並算法
標記-縮並算法是爲了解決內存碎片問題而產生的一種算法。它的整個過程能夠描述爲:標記全部的存活對象;經過從新調整存活對象位置來縮並對象圖;更新指向被移動了位置的對象的指針。
標記階段:
清除階段:
標記-壓縮算法最大的難點在於如何選擇所使用的壓縮算法,若是壓縮算法選擇很差,將會致使極大的程序性能問題,如致使Cache命中率低等。通常來講,根據壓縮後對象的位置不一樣,壓縮算法能夠分爲如下三種:
1. 任意:移動對象時不考慮它們原來的次序,也不考慮它們之間是否有互相引用的關係。
2. 線性:儘量的將原來的對象和它所指向的對象放在相鄰位置上,這樣能夠達到更好的空間局部性。
3. 滑動:將對象「滑動」到堆的一端,把存活對象之間的自由單元「擠出去」,從而維持了分配時的原始次序。
4.節點拷貝算法
節點拷貝算法是把整個堆分紅兩個半區(From,To), GC的過程其實就是把存活對象從一個半區From拷貝到另一個半區To的過程,而在下一次回收時,兩個半區再互換角色。在移動結束後,再更新對象的指針引用,GC開始前的情形:
GC結束後的情形:
節點拷貝算法因爲在拷貝過程當中,就能夠進行內存整理,因此不會再有內存碎片的問題,同時也不須要再專門作一次內存壓縮。,而它最大的缺點在於須要雙倍的空間。
5.總結
本文總共介紹了四種經典的垃圾回收算法,其中後三種常常稱之爲跟蹤垃圾回收,由於引用計數算法可以平滑的進行垃圾回收,而不會出現「中止」現象,常常出現於一些實時系統中,但它沒法解決環形問題;而基於跟蹤的垃圾回收機制,在每一次垃圾回收過程當中,要遍歷或者複製全部的存活對象,這是一個很是耗時的工做,一種好的解決方案就是對堆上的對象進行分區,對不一樣區域的對象使用不一樣的垃圾回收算法,分代式垃圾回收器正是其中一種,CLR和JVM中都採用了分代式垃圾回收機制,但它們在處理上又有些不一樣,後面的文章再詳細介紹這兩種垃圾回收器的區別。
更加詳細請參見於:http://www.cnblogs.com/Terrylee/