最近想複習一下JVM的知識。然後發現網上不少文章在寫JVM的垃圾回收算法時,都比較偏向於具體實現,而少有站在更高角度來看垃圾回收算法的文章。而我本人想對垃圾回收算法有個全景的認識,所以,就找到了這本《垃圾回收的算法與實現》(以下簡稱《垃圾回收》)。本篇博客就是嘗試對“全景”的總結。
以下爲方便討論,垃圾回收縮寫成GC。
爲什麼要有GC
我時而聽到C++程序員說我們是被GC慣壞了的一代。的確是這樣的,我本人在學習GC算法時,大腦裏第一問題就是爲什麼需要GC這樣的東西。說明我已經認爲GC是理所當然了。
總的一句話:沒有GC的世界,我們需要手動進行內存管理,而手動內存管理是純技術活,又容易出錯。
既然我們寫的大多程序都是爲了解決現實業務問題,那麼,我們爲什麼不把這種純技術活自動化呢?但是自動化,也是有代價的。這是我的個人理解,不代表John McCarthy本人的理解。
“垃圾”的定義
首先,我們要給個“垃圾”的定義,才能進行回收吧。書中給出的定義:把分配到堆中那些不能通過程序引用的對象稱爲非活動對象,也就是死掉的對象,我們稱爲“垃圾”。
GC的定義
因爲我們期望讓內存管理變得自動(只管用內存,不管內存的回收),我們就必須做兩件事情: 1. 找到內存空間裏的垃圾;2. 回收垃圾,讓程序員能再次利用這部分空間 。(《垃圾回收》 P2)只要滿足這兩項功能的程序,就是GC,不論它是在JVM中,還是在Ruby的VM中。
但這只是兩個需求,並沒有說明GC應該何時找垃圾,何時回收垃圾等等更具體的問題,各類GC算法就是在這些更具體問題的處理方式上施展手腳。
GC的歷史
John McCarthy身爲Lisp之父和人工智能之父,同時,他也是GC之父。1960年,他在其論文中首次發佈了GC算法(其實是委婉的提出)。
- 標記-清除算法 由John McCarthy在1960年提出
- 引用計數法 由George E. Collins在1960年提出。此算法會有循環引用問題,Harold McBeth 1963年指出。
- 複製算法 由Marvin L. Minsky在1963年提出
《垃圾回收》的作者認爲:
從50年前GC算法首次發佈以來,衆多研究者對其進行了各種各樣的研究,因此許多GC算法也得以發佈。但事實上,這些算法只不過是把前文中提到的三種算法進行組合或應用。也可以這麼說,1963年GC複製算法誕生時,GC的根本性內容就已經完成了。(《垃圾回收》 P4)
那我們常常聽說的分代垃圾回收又是怎麼回事?作者是這樣說的:人們從衆多程序案例中總結出了一個經驗:“大部分的對象在生成後馬上就變成了垃圾,很少有對象能活得很久”。分代垃圾回收利用該經驗,在對象中導入了“年齡”的概念,經歷過一次GC後活下來的對象年齡爲1歲。(垃圾回收》 P141)
分代垃圾回收中把對象分類成幾代,針對不同的代使用不同的GC算法,我們把剛生成的對象稱爲新生代對象,到達一定年齡的對象則稱爲老年代對象。(《垃圾回收》 P142)
好了,這下我總算知道爲什麼要分代了,我的總結是: 將對象根據存活概率進行分類,對存活時間長一些的對象,可以減少掃描“垃圾”的時間,以減少GC頻率和時長。根本思路就是對對象進行分類,才能針對各個分類採用不同的垃圾回收算法,以對各算法進行揚長避短。
留一個問題給讀者:我們知道分代垃圾回收所採用的堆結構是:
爲什麼新生代空間要分成“生成空間”和“倖存空間”,而倖存空間又分成兩塊大小相等的倖存空間1,倖存空間2?
這些GC算法共同解決的問題
上面我們說了,GC的定義只給出了需求,三種算法都爲實現這個需求,那麼它們總會遇到共同要解決的問題吧? 我嘗試總結出:
- 如何分辨出什麼是垃圾?
- 如何、何時搜索垃圾?
- 如何、何時清除垃圾?
這樣,只要涉及到垃圾回收,我就可以從這2點需求,3個共同問題(兩點三共)出發來討論、學習。
如何評價GC算法?
如果沒有評價標準,我們當然無法評估這些GC算法的性能。作者給出了4個標準:
- 吞吐量: 單位時間內的處理能力
- 最大暫停時間:GC執行過程中,應用暫停的時長。較大的吞吐量和較短的最大暫停時間不可兼得
- 堆的使用效率:就是堆空間的利用率。可用的堆越大,GC運行越快;相反,越想有效地利用有限的堆,GC花費的時間就越長。
- 訪問的局部性:把具有引用關係的對象安排在堆中較近的位置,就能提高在緩存中讀取到想利用的數據的概率。
好吧。兩點三共,四標~
小結
搞清楚爲什麼要GC,要實現GC都要解決什麼問題,而各類算法又是怎麼解決的,最後怎麼評價這些算法。GC原來是這麼回事。
但是這不是GC的全部。但是提供我一個思考GC的思考框架。
以上就是《垃圾回收的算法與實現》的讀書筆記。如果想更深入,可以閱讀《垃圾回收算法手冊:自動內存管理的藝術》。
作者:翟志軍
原文鏈接:點擊打開鏈接