複製鏈接
請複製以下鏈接發送給好友

引用計數

鎖定
引用計數是計算機編程語言中的一種內存管理技術,是指將資源(可以是對象內存磁盤空間等等)的被引用次數保存起來,當被引用次數變為零時就將其釋放的過程。使用引用計數技術可以實現自動資源管理的目的。同時引用計數還可以指使用引用計數技術回收未使用資源的垃圾回收算法。
中文名
引用計數
定    義
內存管理技術
相    關
垃圾回收算法
類    型
最直觀的垃圾收集策略
學    科
計算機
領    域
計算機編程語言

引用計數簡介

最直觀的垃圾收集策略是引用計數。引用計數很簡單,但是需要編譯器的重要配合,並且增加了賦值函數 (mutator) 的開銷(這個術語是針對用户程序的,是從垃圾收集器的角度來看的)。每一個對象都有一個關聯的引用計數 —— 對該對象的活躍引用的數量。如果對象的引用計數是零,那麼它就是垃圾(用户程序不可到達它),並可以回收。每次修改指針引用時(比如通過賦值語句),或者當引用超出範圍時,編譯器必須生成代碼以更新引用的對象的引用計數。如果對象的引用計數變為零,那麼運行時就可以立即收回這個塊(並且減少被回收的塊所引用的所有塊的引用計數),或者將它放到遲延收集隊列中。
com組件將維護一個稱作是引用計數的數值。當客户從組件取得一個接口時,此引用計數值將增1。當客户使用完某個接口後,組件的引用計數值將減1.當引用計數值為0時,組件即可將自己從內存中刪除。

引用計數引用計數的使用

引用計數原因

為什麼要選擇為每一個接口單獨維護一個引用計數而不是針對整個組件維護引用計數呢?
主要有兩個原因:一是使程序調試更為方便;另外一個原因是支持資源的按需獲取。
1程序調試:
假設在程序中忘記對某個接口調用Release(其實很多人會犯這個錯)。這樣組件將永遠不會被刪除掉,因為只是在引用計數值0時delete才會被調用 。這時就需要找出接口在何時何處應該被釋放掉。當然找起來是相當困難的。在只對整個組件維護一個接口的情況下,進行這種 查找更為因難了。此時必須檢查使用了此組件所提供的所有接口的代碼。但若組件支持對每個接口分別維護一個引用計數那麼可以把查找的範圍限制在某個特定的接口上。在某些情況下這可以節省大量時間。
2.資源的按需獲取
在實現某個接口時可能需要大量的內存或其他資源。對於此種情況,可以在QueryInterface的實現中,在客户請求此接口時完成資源的分配。但若只對整個組件維護一個引用計數,組件將無法決定何時可以安全地將此些接口相關聯的內存釋放。但基對每個接口分別維護一個引用計數,那麼決定何時可以將此內存釋放將會容易得多。

引用計數規則

正確使用引用計數三條簡單的規則
1. 在返回之前調用AddRef。對於那些建好些返回接口指針的函數,在返回之前應該相應的指針調用AddRef。這些函數包括QueryInterface 及CreateInstance。這樣當客户從這種 函數得到一個接口後。它將無需調用AddRef.
2.使用完接口之後調用Release。在使用某個接口之後應該調用些接口的Release函數。
3.在賦值之後調用AddRef. 在將一個接口指針賦給另一個接口指針時,應調用AddRef。換句話説,在建立接口的別外一個引用之後應增加相應組件的引用計數。

引用計數接口

在客户看來,引用計數是處於接口級的而不是組件級的。但從實現的角度來看,誰的引用計數被記錄下來實際上沒有關係。客户可以一直接相信組件將記錄每個接口本身維護引用計數值。但客户不能假設整個組件維護單個的引用計數。
對於客户而言,每一個接口被分別維護一個引用計數意味着客户應該對它將要使用的指針調用AddRef,而不是其他的什麼指針。對於使用完了指針客户應該調用其Release
選擇為每一個接口單獨維護一個引用計數而不是針對整個組件維護引用計數的原因:
使程序調試更為方便;支持資源的按需獲取;

引用計數調試

可以通過增大和減少某個數的值而實現之。
另外要注意的是AddRef和Release的返回值沒有什麼意義,只是在程序調試中才可能會用得上.客户不應將此從此值當成是組件或其接口的精確引用數。

引用計數規則

客户必須對每一個接口具有一個單獨的引用計數值那樣來處理各接口。因此,客户必須對不同的接口分別進行引用計數,即使它們的生命期是嵌套的。
一、輸出參數規則
輸出參數指的是給函數的調用者傳回一個值的函數參數。從這一點上講,輸出參數的作用同函數的返回值是類似的。任何在輸出參數中或作為返回值返回一個新的接口指針的函數必須對些接口指針調用AddRer。
二、輸入參數規則
對傳入函數的接口指針,無需調用AddRef和Release,這是因為函數的生命期嵌套在調用者的生命期內。
三、輸入-輸出參數規則
輸入-輸出參數同時具有輸入參數及輸出參數的功能。在函數休中可以使用輸入-輸出參數的值,然後可以對這些值進行修改並將其返回給調用者。
在函數中,對於用輸入-輸出參數傳遞進來的接口指針,必須在給它賦另外一個接口指針值之前調用其Release。在函數返回之前,還必須對輸出參數中所保存的接口指針調用AddRef。
四、局部變量規則
對於局部自制的接口指針,由於它們只是在函數的生命其內才存在,因此無需調用AddRef和Release。這條規則實際是輸入參數規則的直接結果。在下面的例子中,pIX2只是在函數foo的生命期內都在,因此可以保證其生命期將嵌套在所傳入的pIX指針的生命期,因此無需對pIX2調用AddRef和Release
五、全局變量規則
對於保存在全局變量中的接口指針,在將其傳遞給另外一個函數之前,必須調用其AddRef。由於此變量是全局性的,因此任何函數都可以通過調用其Release來終止其生命期。對於保存在成員變量中的接口指針,也應按此種方式進行處理。因為類中的任何成員函數都可以改變此種接口指針的狀態。
六、不能確定時的規則
對於任何不定的情形,都應調用AddRef和Release對。
另外,在決定要進行優化時,應給那些沒有進行引用計數的指針加上相應的註釋,否則其它程序員在修改代碼時,將可能會增大接口指針的生命期,從而合引用計數的優化遭到破壞。
忘記調用Release造成的錯誤可能比不調用AddRef造成的錯誤更難檢測。

引用計數優勢與缺陷

與跟蹤式垃圾回收相比,引用計數的主要優點是可以儘快地回收不再被使用的對象,同時在回收過程中不會導致長時間的停頓,還可以清晰地標明每一個對象的生存週期。
實時應用或內存受限的系統中,實時響應能力是一項重要指標,而引用計數作為最容易實現的垃圾回收技術之一,很適合於這種情況。引用計數還可以用於管理其他非內存資源,如操作系統對象(經常比內存資源更稀缺)。跟蹤式垃圾回收技術用終結器處理此類目標,但延遲迴收可能引發其他問題。加權引用計數是適用於分佈式系統的派生技術。
在可用內存被活躍對象填滿的平台上,跟蹤式垃圾回收會被頻繁觸發,從而降低性能。而引用計數即便在內存瀕臨耗盡的情況下性能依然有所保障。引用計數還能為其他運行時優化技術提供參考信息,例如對於許多使用不可變對象的系統來説(如函數式編程語言),大量複製對象導致的性能懲罰有時十分嚴重;在此類系統上一個典型的優化措施是:假如一個對象被創建以後僅使用了一次,且在其不再被引用的同時另一個類似的對象被創建出來(如Javascript中的字符串拼接賦值操作),可以將刪除原對象創建新對象的行為變為修改原對象,從而提高效率。引用計數可以為這類優化提供充分的參考信息。 [1] 
未經優化的引用計數相比跟蹤式垃圾回收有兩個主要缺點,都需要引入附加機制予以修復:
  • 頻繁更新引用計數會降低運行效率。
  • 原始的引用計數無法解決循環引用問題。
另外,如果使用空閒列表分配內存,那麼引用計數的空間局域性非常差。僅使用引用計數無法通過移動對象來提高CPU緩存的性能,所以高性能的內存分配器都會同時實現一個跟蹤式垃圾回收器以提高性能。許多引用計數實現(比如PHP和Objective-C)的性能不佳都是因為沒有實現內存拷貝。 [2] 
參考資料
  • 1.    Wilson, Paul R. "Uniprocessor Garbage Collection Techniques". Proceedings of the International Workshop on Memory Management. London, UK: Springer-Verlag. pp. 1–42. ISBN 3-540-55940-X. Retrieved 5 December 2009. Section 2.1.
  • 2.    Rifat Shahriyar, Stephen M. Blackburn, Xi Yang and Kathryn S. McKinley (2013). "Taking Off the Gloves with Reference Counting Immix". 24th ACM SIGPLAN conference on Object Oriented Programming Systems, Languages and Applications. OOPSLA 2013