週一晚上下班,我高高興興的回到家裡面,女朋友蹦蹦跳跳的朝我跑過來,手裡拿著掃把和拖布。這是又要打我麼?我又做錯了什麼事情麼?我大腦在高速旋轉。這時,女朋友打破了沉默。
Garbage Collection,簡稱GC,中文名"垃圾回收"。是和計算機記憶體管理有關的概念,這裡面的垃圾指的是程式不用的記憶體空間。
什麼是垃圾回收
說到做家務,我們肯定不免要丟棄一些東西,說的文明一點叫斷舍離,說的簡單一點就是丟垃圾。
在現實世界中,說到垃圾,指的就是那些不讀的書、不穿的衣服等。這種情況下的"垃圾"指的就是"自己不用的東西"。我們在整理家務的時候,一般是要做兩件事,找到家裡不用的垃圾,把這些垃圾丟棄,以便放一些其他的有用的東西。
對映到計算機系統中也一樣,計算機的記憶體也是有限的,不可能把所有東西都一直存放在記憶體中,也需要定期釋放不用的記憶體空間。而這些不用的記憶體空間中存放的東西就是垃圾了。在程式中,垃圾回收的過程就是找到記憶體空間中的垃圾,然後進行垃圾回收,讓程式設計師能夠再次利用這部分空間。
什麼樣的東西算是垃圾
前面我們提到過,生活中的垃圾就是那些不用的東西。但是,『不用』這件事是如何確定的呢?
在日常做家務的時候,我們想要確定一個東西是否可以丟棄的時候,我們會有很多方法。
引用計數演算法
第一種,我們在房間內找到一個感覺沒什麼用的usb線的時候,我們是這樣判斷他有沒有用的:
1、看家裡有沒有可以用得上這個充電口的裝置。
2、看家裡有沒有可以適配這個USB線的介面卡。
如果有的話,那麼我們就認為這根線是有用的,否則,這根USB線就會被我們標記為垃圾。等待被丟棄。
上面這種方式,在計算機的垃圾手機演算法中叫做引用計數法
,其演算法過程是這樣的:給物件中增加一個引用計數器,每當有一個地方引用他時,計數器就加1,當引用失效時,計數器值就減1。當執行垃圾回收時,只需要判斷這個物件的引用計數器的數值是不是0就可以了。如果引用計數器數值為0,則表示可以回收。
這是一種比較簡單的演算法了,這種垃圾回收方式比較簡單。
但是,這種丟垃圾的方式有一個缺點,那就是有可能效果不明顯,就像我們想要丟棄一個USB線的時候,發現只有一個MP3可以使用他,然後,我們就把USB線保留下來了。當我們想要丟棄MP3的時候,發現家裡還有一根USB線可以用到他,這樣,MP3也被保留下來了。
但是,如果這個MP3和USB線根本就沒有人想要用了呢?比如這個USB線和MP3是家裡的某個客人留下的,他表示已經不需要了呢?
這就是引用計數法的缺點,就是如果存在迴圈引用物件,將導致無法回收。
可達性分析演算法
當然,日常生活中,我們判斷一個東西還有沒有用,不能僅僅看是不是有東西和他"配套",還是要看家裡人到底還用不用得到。
所以,比較靠譜一點的判斷一個東西是不是垃圾的時候,我們會拿著一個東西,問一遍家裡的所有成員:這東西你還需要嗎?
如果得到的答案都是不需要的話,那就證明這個東西可以丟棄了。這樣就避免了MP3和USB線被誤保留的尷尬。
這種方式,就是從家庭成員出發,去判斷一個東西到底有沒有用。而不是從物品之間的相關關聯關係來判斷。
上面這種垃圾判斷的方法,在計算機中叫做可達性分析演算法
,這個演算法的基本思路是通過一系列的"GC Root"的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑成為引用鏈,當一個物件到GC Root沒有任何引用鏈相連時,則證明此物件是不可用的。
一個物品,沒有任何家庭成員宣佈需要還要繼續使用。就像一個物件,到達所有的"GC Root"都沒有引用鏈是一樣的。
在Java語言中,可以作為GC Root的物件包括以下幾種:
1、虛擬機器棧中引用的物件。
2、方法區中類靜態屬性引用的物件。
3、方法區中常量引用的物件。
4、本地方法棧中JNI引用的物件。
垃圾的宿命
一般情況下,我們對於一個家裡面沒用的東西處理,不太會果斷的直接扔掉。有的時候對於一些有一定紀念意義的、或者比較貴重的東西會先保留一段時間,經過幾次清理,還是覺得沒用以後,才會被徹底扔掉。
其實,計算機的垃圾回收也是一樣的。就算一個物件,通過可達性分析演算法分析後,發現其是『不可達』的,也並不是非回收不可的。
一般情況下,要宣告一個物件死亡,至少要經過兩次標記過程:
1、經過可達性分析後,一個物件並沒有與GC Root關聯的引用鏈,將會被第一次標記和篩選。篩選條件是此物件有沒有必要執行finalize()方法。如果物件沒有覆蓋finalize()方法,或者已經執行過了。那就認為他可以回收了。如果有必要執行finalize()方法,那麼將會把這個物件放置到F-Queue的佇列中,等待執行。
2、虛擬機器會建立一個低優先順序的Finalizer執行緒執行F-Queue裡面的物件的finalize()方法。如果物件在finalize()方法中可以『拯救』自己,那麼將不會被回收,否則,他將被移入一個即將被回收的物件集合。
物件如何在finalize()中『拯救』自己呢?
最簡單的方式就是重新簡歷引用,比如把自己賦值給某個類變數或者物件的成員變數。