JVM(四)--GC的三大高級算法


C的基本算法,大體上都逃不出 標記清除法/標記壓縮法、復制收集算法、引用計數法 這三種方式以及它們的衍生品。現在,通過對這三種方式進行融合,出現了一些更加高級的方式。這里,我們介紹一下其中最有代表性的三種,即分代回收、增量回收和並行回收。有些情況下,也可以對這些方法中的幾種進行組合使用。

1、分代回收
首先,我們來講講高級GC技術中最重要的一種,即分代回收(Generational GC)。由於GC和程序處理的本質是無關的,因此它所消耗的時間越短越好。分代回收的目的,正是為了在程序運行期間,將GC所消耗的時間盡量縮短。 分代回收的基本思路,是利用了一般性程序所具備的性質,即大部分對象都會在短時間內成為垃圾,而經過一定時間依然存活的對象往往擁有較長的壽命 。如果壽命長的對象更容易存活下來,壽命短的對象則會被很快廢棄,那么到底怎樣做才能讓GC變得更加高效呢?如果對分配不久,誕生時間較短的“年輕”對象進行重點掃描,應該就可以更有效地回收大部分垃圾。
在分代回收中,對象按照生成時間進行分代,剛剛生成不久的年輕對象划為新生代(Young gen-eration),而存活了較長時間的對象划為老生代(Old generation)。根據具體實現方式的不同,可能還會划分更多的代,在這里為了講解方便,我們就先限定為兩代。如果上述關於對象壽命的假說成立的話,那么只要僅僅掃描新生代對象,就可以回收掉廢棄對象中的很大一部分。像這種只掃描新生代對象的回收操作,被稱為小回收(Minor GC)。小回收的具體回收步驟如下。首先從根開始一次常規掃描,找到“存活”對象。這個步驟采用標記清除或者是復制收集算法都可以,不過大多數分代回收的實現都采用了復制收集算法。需要注意的是,在掃描的過程中,如果遇到屬於老生代的對象,則不對該對象繼續進行遞歸掃描。這樣一來,需要掃描的對象數量就會大幅減少。然后,將第一次掃描后殘留下來的對象划分到老生代。具體來說,如果是用復制收集算法的話,只要將復制目標空間設置為老生代就可以了;而用標記清除算法的話,則大多采用在對象上設置某種標志的方式。

從任何地方都沒有進行引用的老生代中的F對象,會通過大回收操作進行回收。

對來自老生代的引用進行記錄這個時候,問題出現了, 從老生代對象對新生代對象的引用怎么辦呢?如果只掃描新生代區域的話,那么從老生代對新生代的引用就不會被檢測到。這樣一來,如果一個年輕的對象只有來自老生代對象的引用,就會被誤認為已經“死亡”了。因此,在分代回收中,會對對象的更新進行監視,將從老生代對新生代的引用,記錄在一個叫做記錄集(remembered set)的表中(圖5)。在執行小回收的過程中,這個記錄集也作為一個根來對待 。

要讓分代回收正確工作,必須使記錄集的內容保持更新。為此, 在老生代到新生代的引用產生的瞬間,就必須對該引用進行記錄,而負責執行這個操作的子程序,需要被嵌入到所有涉及對象更新操作的地方。這個負責記錄引用的子程序是這樣工作的 。設有兩個對象:A和B,當對A的內容進行改寫,並加入對B的引用時, 如果①A屬於老生代對象,②B屬於新生代對象,則將該引用添加到記錄集中。這種檢查程序需要對所有涉及修改對象內容的地方進行保護,因此被稱為寫屏障(Write barrier) 。寫屏障不僅用於分代回收,同時也用在很多其他的GC算法中。雖說老生代區域中的對象一般來說壽命都比較長,但也決不是“不老不死”的。隨着程序的運行,老生代區域中的“死亡”對象也在不斷增加。為了避免這些死亡的老生代對象白白占用內存空間,偶爾需要對包括老生代區域在內的全部區域進行一次掃描回收。像這樣以全部區域為對象的GC操作被稱為完全回收(Full GC)或者大回收(Major GC)。分代回收通過減少GC中掃描的對象數量,達到縮短GC帶來的平均中斷時間的效果。不過由於還是需要進行大回收,因此最大中斷時間並沒有得到什么改善。從吞吐量來看,在對象壽命假說能夠成立的程序中,由於掃描對象數量的減少,可以達到非常不錯的成績。但是,其性能會被程序行為、分代數量、大回收觸發條件等因素大幅度左右。

2、增量回收
在對實時性要求很高的程序中,比起縮短GC的平均中斷時間,往往更重視縮短GC的最大中斷時間。例如,在機器人的姿勢控制程序中,如果因為GC而讓控制程序中斷了0.1秒,機器人可能就摔倒了。或者,如果車輛制動控制程序因為GC而延遲響應的話,后果也是不堪設想的。在這些對實時性要求很高的程序中,必須能夠對GC所產生的中斷時間做出預測。例如,可以將“最多只能中斷10毫秒”作為附加條件。在一般的GC算法中,作出這樣的保證是不可能的,因為GC產生的中斷時間與對象的數量和狀態有關。

因此,為了維持程序的實時性,不等到GC全部完成,而是將GC操作細分成多個部分逐一執行。這種方式被稱為增量回收(Incremental GC)。在增量回收中,由於GC過程是漸進的,在回收過程中程序本身會繼續運行,對象之間的引用關系也可能會發生變化。如果已經完成掃描和標記的對象被修改,對新的對象產生了引用,這個新對象就不會被標記,明明是“存活”對象卻被回收掉了。在增量回收中為了避免這樣的問題,和分代回收一樣也采用了寫屏障。當已經被標記的對象的引用關系發生變化時,通過寫屏障會將新被引用的對象作為掃描的起始點記錄下來。由於增量回收的過程是分步漸進式的,可以將中斷時間控制在一定長度之內。另一方面,由於中斷操作需要消耗一定的時間,GC所消耗的總時間就會相應增加,正所謂有得必有失。

3、並行回收
最近的計算機中,一塊芯片上搭載多個CPU核心的多核處理器已經逐漸普及。不僅是服務器,就連個人桌面電腦中,多核CPU也已經成了家常便飯。例如美國英特爾公司的Core i7就擁有6核12個線程。在這樣的環境中,就需要通過利用多線程來充分發揮多CPU的性能。並行回收正是通過最大限度利用多CPU的處理能力來進行GC操作的一種方式。 並行回收的基本原理是,是在原有的程序運行的同時進行GC操作 ,這一點和增量回收是相似的。不過,相對於在一個CPU上進行GC任務分割的增量回收來說,並行回收可以利用多CPU的性能,盡可能讓這些GC任務並行(同時)進行。由於軟件運行和GC操作是同時進行的,因此就會遇到和增量回收相同的問題。為了解決這個問題,並行回收也需要用寫屏障來對當前的狀態信息保持更新。不過,讓GC操作完全並行,而一點都不影響原有程序的運行,是做不到的。因此在GC操作的某些特定階段,還是需要暫停原有程序的運行。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com