GC演算法 引用計數法

2022-05-10 21:08:02 字數 1697 閱讀 1471

引用計數法又是什麼鬼呢? 顧名思義, 對物件的引用進行計數. 通過記錄每個物件被引用的次數, 來確定這個物件是否可以被**.

首先, 對物件的引用數量進行管理, 什麼時候會更新呢?

建立物件: 新建乙個物件(對這個新的物件引用數量+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的就記不下了. 這個方法對超出範圍的處理方式很簡單, 什麼都不做, 不去**, 畢竟被引用這麼多次, 該物件定然很重要. 那這些物件不就永遠都不能被**了麼? 可以, 等到沒有記憶體了, 使用標記清除演算法將所有物件過一遍.

當然, 針對引用計數法還有很多演變, 有些還是很有意思的, 有些是我看不懂的.

垃圾**的整體思路分兩個流派(我所知道的):

引用計數: 就是上面說的這種

可達性: 就是標記清除那種, 判斷乙個物件是否可以到達.

引用計數的最大優勢應該就是不需要暫停程式去進行**了, 隨使用隨**. 但劣勢也很明顯: 需要計數器額外空間以及迴圈引用的問題.

個人是比較喜歡引用計數的, 實時性又高, 又不需要太多的額外空間. 只是需要在編寫**的時候刻意規避迴圈引用, 或者其他方法規避一下? 甚至不去處理都刻意, 如果只有少數的話(如果有很多, 還是換個演算法吧).

自動引用計數

每當你產生乙個物件,arc分配一大塊控制項去儲存這個物件。除此之外,當乙個物件是不在需要,arc 將會收回這個例項物件所佔的空間。如果乙個物件的空間被釋放掉,但是你任然通過物件的引用呼叫方法或者屬性,你的程式將要崩潰。為了不讓物件被釋放掉 arc將會追蹤有多少屬性 常量 變數正在引用這個物件。只要有...

引用計數 copy on write

拷貝通常分為值拷貝也叫淺拷貝和深拷貝,當拷貝方式為淺拷貝時就會有以下兩個問題 1 當需要調析構函式時,會出現同一塊空間被析構兩次的情況 2 如果是對指標的淺拷貝,會讓兩個指標同時管理同一塊空間,當乙個指針對這塊空間進行修改時,會影響到另外乙個指標。通過引用計數加寫時拷貝可以有效地解決這些問題。引用計...

自動引用計數

使用弱引用解決迴圈強引用 如果兩個變數屬性都可以設定為nil,那麼就用弱引用來設定其中乙個屬性來解決迴圈強引用。class person weak var apartment apartment?deinit class apartment deinit var aperson person?per...