怎麼解決引用計數 GC 的迴圈引用問題?

augfun發表於2020-11-18

引用計數方式 GC 存在迴圈引用問題,導致無法辨別無用物件,而 GC ROOT 方式不存在迴圈引用的問題

引用計數和 GC ROOT 的實現機理很易理解,面試時大家都能流利應答,那怎麼才能脫穎而出呢?思考一個問題:不通過 GC ROOT,仍使用引用計數方式,怎麼解決它的迴圈引用問題?

解答此問題前,通過目標驅動法來想象一下,若 Get 了此知識點,可以這樣應用到面試中:

面試官: 說一下垃圾回收機制吧

我: ...可以通過強、弱引用計數結合方式解決引用計數的迴圈引用問題,實際上 Android 的智慧指標就是這樣實現的...

智慧指標

智慧指標在整個 Android 工程中使用很廣泛,在 binder 相關原始碼可以看到 sp、wp 型別的引用:

 sp<IBinder> result = new BpBinder(handle);

 wp<IBinder> result = new BpBinder(handle);

sp 即 strong pointer 強指標引用;wp 是 weak pointer 弱指標引用。

在 Java 中我們不用關心物件的銷燬及記憶體釋放,GC 機制會自動辨別回收無用物件,而 智慧指標 就是 native 層一個小型的 GC 實現。

智慧指標以引用計數的方式來標識無用物件,使用智慧指標的物件需繼承自 RefBase,RefBase 中維護了此物件的強引用數量和弱引用數量。

強指標 sp 過載了 "=" 運算子,在引用其他物件時將強引用計數 +1,在 sp 解構函式中將強引用計數 -1,當強引用計數減至 0 時銷燬引用的物件,這樣就實現了物件的自動釋放。

弱指標引用其他物件時將弱引用計數 +1,在 wp 解構函式中將弱引用計數 -1,當強引用計數為 0 時,不論弱引用計數是否為 0 都銷燬引用的物件。

如何解決迴圈引用問題

只靠強引用計數方式,會存在迴圈引用的問題,導致物件永遠無法被釋放,弱引用就是專門用來解決迴圈引用問題的:

若 A 強引用了 B,那 B 引用 A 時就需使用弱引用,當判斷是否為無用物件時僅考慮強引用計數是否為 0,不關心弱引用計數的數量

這樣就解決了迴圈引用導致物件無法釋放的問題,但這會引發野指標問題:當 B 要通過弱指標訪問 A 時,A 可能已經被銷燬了,那指向 A 的這個弱指標就變成野指標了。在這種情況下,就表示 A 確實已經不存在了,需要進行重新建立等其他操作

智慧指標自定義規則

智慧指標並不是固定的 "當強引用計數為 0 時,不論弱引用計數是否為 0 都銷燬引用的物件" ,而是可以自定義規則。RefBase 提供了 extendObjectLifetime() 方法,可以用來設定引用計數器的規則,不同規則對刪除目標物件的時機判斷也是不一樣的,包括以下三種規則:

  • OBJECT_LIFETIME_STRONG:只有在這個物件記憶體空間中的強計數器值為 0 的時候才會銷燬物件

  • OBJECT_LIFETIME_WEAK:只有在這個物件記憶體空間中的強計數器和弱計數器的值都為 0 的時候才會銷燬物件

  • OBJECT_LIFETIME_MASK:不管這兩個計數器是不是都為 0,都不銷燬物件,即與一般指標無異,還是要自己手動去釋放物件



作者:位元組走動_Android
連結:https://www.jianshu.com/p/519a2b76d4d4
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

相關文章