C++中经典的垃圾回收算法
1.引用計數(shù)算法
???????? 引用計數(shù)(Reference Counting)算法是每個對象計算指向它的指針的數(shù)量,當(dāng)有一個指針指向自己時計數(shù)值加1;當(dāng)刪除一個指向自己的指針時,計數(shù)值減1,如果計數(shù)值減為0,說明已經(jīng)不存在指向該對象的指針了,所以它可以被安全的銷毀了。可以很直觀的用下面的圖表示:
?
?
????? 引用計數(shù)算法的優(yōu)點在于內(nèi)存管理的開銷分布于整個應(yīng)用程序運行期間,非常的“平滑”,無需掛起應(yīng)用程序的運行來做垃圾回收;而它的另外一個優(yōu)勢在于空間上的引用局部性比較好,當(dāng)某個對象的引用計數(shù)值變?yōu)?時,系統(tǒng)無需訪問位于堆中其他頁面的單元,而后面我們將要看到的幾種垃圾回收算法在回收前都回遍歷所有的存活單元,這可能會引起換頁(Paging)操作;最后引用計數(shù)算法提供了一種類似于棧分配的方式,廢棄即回收,后面我們將要看到的幾種垃圾回收算法在對象廢棄后,都會存活一段時間,才會被回收。
???? 引用計數(shù)算法有著諸多的優(yōu)點,但它的缺點也是很明顯的。首先能看到的一點是時間上的開銷,每次在對象創(chuàng)建或者釋放時,都要計算引用計數(shù)值,這會引起一些額外的開銷;第二是空間上的開銷,由于每個對象要保持自己被引用的數(shù)量,必須付出額外的空間來存放引用計數(shù)值;引用計數(shù)算法最大的缺點就在于它無法處理環(huán)形引用,如下圖所示:
?????? 此 處藍(lán)色的這兩個對象既不可達也無法回收,因為彼此之間互相引用,它們各自的計數(shù)值都不為0,這種情況對引用計數(shù)算法來說是無能為力的,而其他的垃圾回收算法卻能很好的處理環(huán)形引用。
引用計數(shù)算法最著名的運用,莫過于微軟的COM技術(shù),大名鼎鼎的IUnknown接口:
[cpp]?view plaincopy
?
其中的AddRef和Release就是用來讓組件自己管理其生命周期,而客戶程序只關(guān)心接口,而無須再去關(guān)心組件的生命周期,一個簡單的使用示例如下:
[cpp]?view plaincopy
上面的客戶程序在CreateInstance中已經(jīng)調(diào)用過AddRef,所以無需再次調(diào)用,而在使用完接口后調(diào)用Release,這樣組件自己維護的計數(shù)值將會改變。下面代碼給出一個簡單的實現(xiàn)AddRef和Release示例:
[cpp]?view plaincopy
?
???? 在編程語言Python中,使用也是引用計數(shù)算法,當(dāng)對象的引用計數(shù)值為0時,將會調(diào)用__del__函數(shù),至于為什么Python要選用引用計數(shù)算法,據(jù)我看過的一篇文章里面說,由于Python作為腳本語言,經(jīng)常要與C/C++這些語言交互,而使用引用計數(shù)算法可以避免改變對象在內(nèi)存中的位置,而Python為了解決環(huán)形引用問題,也引入gc模塊,所以本質(zhì)上Python的GC的方案是混合引用計數(shù)和跟蹤(后面要講的三個算法)兩種垃圾回收機制。
2.標(biāo)記-清除算法
標(biāo)記-清除(Mark-Sweep)算法依賴于對所有存活對象進行一次全局遍歷來確定哪些對象可以回收,遍歷的過程從根出發(fā),找到所有可達對象,除此之外,其它不可達的對象就是垃圾對象,可被回收。整個過程分為兩個階段:標(biāo)記階段找到所有存活對象;清除階段清除所有垃圾對象。
標(biāo)記階段
?
清除階段
?
????? 相比較引用計數(shù)算法,標(biāo)記-清除算法可以非常自然的處理環(huán)形引用問題,另外在創(chuàng)建對象和銷毀對象時時少了操作引用計數(shù)值的開銷。它的缺點在于標(biāo)記-清除算法是一種“停止-啟動”算法,在垃圾回收器運行過程中,應(yīng)用程序必須暫時停止,所以對于標(biāo)記-清除算法的研究如何減少它的停頓時間,而分代式垃圾收集器就是為了減少它的停頓時間,后面會說到。另外,標(biāo)記-清除算法在標(biāo)記階段需要遍歷所有的存活對象,會造成一定的開銷,在清除階段,清除垃圾對象后會造成大量的內(nèi)存碎片。
3.標(biāo)記-縮并算法
???? 標(biāo)記-縮并算法是為了解決內(nèi)存碎片問題而產(chǎn)生的一種算法。它的整個過程可以描述為:標(biāo)記所有的存活對象;通過重新調(diào)整存活對象位置來縮并對象圖;更新指向被移動了位置的對象的指針。
標(biāo)記階段:
?
清除階段:
?
標(biāo)記-壓縮算法最大的難點在于如何選擇所使用的壓縮算法,如果壓縮算法選擇不好,將會導(dǎo)致極大的程序性能問題,如導(dǎo)致Cache命中率低等。一般來說,根據(jù)壓縮后對象的位置不同,壓縮算法可以分為以下三種:
1. 任意:移動對象時不考慮它們原來的次序,也不考慮它們之間是否有互相引用的關(guān)系。?
2. 線性:盡可能的將原來的對象和它所指向的對象放在相鄰位置上,這樣可以達到更好的空間局部性。?
3. 滑動:將對象“滑動”到堆的一端,把存活對象之間的自由單元“擠出去”,從而維持了分配時的原始次序。
4.節(jié)點拷貝算法
節(jié)點拷貝算法是把整個堆分成兩個半?yún)^(qū)(From,To), GC的過程其實就是把存活對象從一個半?yún)^(qū)From拷貝到另外一個半?yún)^(qū)To的過程,而在下一次回收時,兩個半?yún)^(qū)再互換角色。在移動結(jié)束后,再更新對象的指針引用,GC開始前的情形:
GC結(jié)束后的情形:
????? 節(jié)點拷貝算法由于在拷貝過程中,就可以進行內(nèi)存整理,所以不會再有內(nèi)存碎片的問題,同時也不需要再專門做一次內(nèi)存壓縮。,而它最大的缺點在于需要雙倍的空間。
5.總結(jié)
????? 本文總共介紹了四種經(jīng)典的垃圾回收算法,其中后三種經(jīng)常稱之為跟蹤垃圾回收,因為引用計數(shù)算法能夠平滑的進行垃圾回收,而不會出現(xiàn)“停止”現(xiàn)象,經(jīng)常出現(xiàn)于一些實時系統(tǒng)中,但它無法解決環(huán)形問題;而基于跟蹤的垃圾回收機制,在每一次垃圾回收過程中,要遍歷或者復(fù)制所有的存活對象,這是一個非常耗時的工作,一種好的解決方案就是對堆上的對象進行分區(qū),對不同區(qū)域的對象使用不同的垃圾回收算法,分代式垃圾回收器正是其中一種,CLR和JVM中都采用了分代式垃圾回收機制,但它們在處理上又有些不同,后面的文章再詳細(xì)介紹這兩種垃圾回收器的區(qū)別。
?更加詳細(xì)請參見于:http://www.cnblogs.com/Terrylee/
總結(jié)
以上是生活随笔為你收集整理的C++中经典的垃圾回收算法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 简述数学建模的过程_数学建模的一般步骤
- 下一篇: C++跨平台开源库 之二