概述
引用計數法又是什麼鬼呢? 顧名思義, 對對象的引用進行計數. 通過記錄每個對象被引用的次數, 來確定這個對象是否可以被回收.
實現
首先, 對對象的引用數量進行管理, 什麼時候會更新呢?
- 創建對象: 新建一個對象(對這個新的對象引用數量+1)
- 更新指針: 將一個指向A對象的指針重新指向B對象(將A對象引用數量-1, B對象引用數量+1)
這次就不上代碼了, 簡單介紹一下思路就行. (我哥說代碼看着費勁)
前提: 我們有一個全局的空閒地址鏈表: FREE_HEAD
創建對象的操作
- 從FREE_HEAD中尋找內存
- 若找到了, 該對象計數器置爲1, 返回
- 若沒有找到, 內存擴容, 返回1
更新指針的操作
- 將新的對象引用計數+1
- 將舊的對象引用計數-1. 若-1後引用數量爲0, 則將該對象及所有的子對象添加到
FREE_HEAD
鏈表中.
實現說起來簡簡單單, 畢竟我也不用真的去實現, 簡單想一下.
分析
在上一次的標記清除算法
中, GC在每次內存不足時運行, 勢必會導致程序暫停時間比較長. 但引用計數
則在每次指針變更的同時進行管理, 在產生新的垃圾的時候立刻進行回收. 這就體現出它的幾個優勢了:
- 最大暫停時間短.
- 產生垃圾可立即回收
當然, 只說優勢不說劣勢都是扯犢子. 首先, 引用計數
的優勢也會成爲它的劣勢, 計數頻繁的計算, 會拖累程序的速度. 而且每個對象都要開拓空間來保存引用數量. 當然了, 還有經常被說到的循環引用的問題. 等等吧.
- 頻繁的更新引用計數拖累程序速度
- 每個對象需要開拓額外空間保存引用計數
- 循環引用對象無法被回收(就是A引用B, B引用A. 但是他們都沒有被其他對象引用, 導致他倆的引用始終爲1, 無法回收)
當然, 針對問題, 偉大的前人總是有辦法去解決. 比如:
延遲計數法: 針對頻繁更新計數器的問題而提出的. 大概意思就是不去實時的對引用數量進行更新, 將引用數量爲0的記錄到一個待處理的鏈表中, 當需要新的內存時再統一處理. 但是這樣又會增大暫停時間, 纔不要.
Sticky引用計數法: 引用計數通過額外的空間保存引用數量, 但這個必然會有最大值, 比如用1個字節, 則引用數量超過256的就記不下了. 這個方法對超出範圍的處理方式很簡單, 什麼都不做, 不去回收, 畢竟被引用這麼多次, 該對象定然很重要. 那這些對象不就永遠都不能被回收了麼? 可以, 等到沒有內存了, 使用標記清除算法
將所有對象過一遍.
當然, 針對引用計數法還有很多演變, 有些還是很有意思的, 有些是我看不懂的.
垃圾回收的整體思路分兩個流派(我所知道的):
- 引用計數: 就是上面說的這種
- 可達性: 就是
標記清除
那種, 判斷一個對象是否可以到達.
引用計數
的最大優勢應該就是不需要暫停程序去進行回收了, 隨使用隨回收. 但劣勢也很明顯: 需要計數器額外空間以及循環引用的問題.
個人是比較喜歡引用計數
的, 實時性又高, 又不需要太多的額外空間. 只是需要在編寫代碼的時候刻意規避循環引用, 或者其他方法規避一下? 甚至不去處理都刻意, 如果只有少數的話(如果有很多, 還是換個算法吧).