jvm系列算法
GC的基本算法,大致上都逃不出標記清除法/標記壓縮法、複製收集算法、引用計數法這三種方式以及它們的衍生品。如今,經過對這三種方式進行融合,出現了一些更加高級的方式。這裏,咱們介紹一下其中最有表明性的三種,即分代回收、增量回收和並行回收。有些狀況下,也能夠對這些方法中的幾種進行組合使用。
首先,咱們來說講高級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帶來的平均中斷時間的效果。不過因爲仍是須要進行大回收,所以最大中斷時間並無獲得什麼改善。從吞吐量來看,在對象壽命假說可以成立的程序中,因爲掃描對象數量的減小,能夠達到很是不錯的成績。可是,其性能會被程序行爲、分代數量、大回收觸發條件等因素大幅度左右。
在對實時性要求很高的程序中,比起縮短GC的平均中斷時間,每每更重視縮短GC的最大中斷時間。例如,在機器人的姿式控制程序中,若是由於GC而讓控制程序中斷了0.1秒,機器人可能就摔倒了。或者,若是車輛制動控制程序由於GC而延遲響應的話,後果也是不堪設想的。在這些對實時性要求很高的程序中,必須可以對GC所產生的中斷時間作出預測。例如,能夠將「最多隻能中斷10毫秒」做爲附加條件。在通常的GC算法中,做出這樣的保證是不可能的,由於GC產生的中斷時間與對象的數量和狀態有關。
所以,爲了維持程序的實時性,不等到GC所有完成,而是將GC操做細分紅多個部分逐一執行。這種方式被稱爲增量回收(Incremental GC)。在增量回收中,因爲GC過程是漸進的,在回收過程當中程序自己會繼續運行,對象之間的引用關係也可能會發生變化。若是已經完成掃描和標記的對象被修改,對新的對象產生了引用,這個新對象就不會被標記,明明是「存活」對象卻被回收掉了。在增量回收中爲了不這樣的問題,和分代回收同樣也採用了寫屏障。當已經被標記的對象的引用關係發生變化時,經過寫屏障會將新被引用的對象做爲掃描的起始點記錄下來。因爲增量回收的過程是分步漸進式的,能夠將中斷時間控制在必定長度以內。另外一方面,因爲中斷操做須要消耗必定的時間,GC所消耗的總時間就會相應增長,正所謂有得必有失。
最近的計算機中,一塊芯片上搭載多個CPU核心的多核處理器已經逐漸普及。不只是服務器,就連我的桌面電腦中,多核CPU也已經成了屢見不鮮。例如美國英特爾公司的Core i7就擁有6核12個線程。在這樣的環境中,就須要經過利用多線程來充分發揮多CPU的性能。並行回收正是經過最大限度利用多CPU的處理能力來進行GC操做的一種方式。並行回收的基本原理是,是在原有的程序運行的同時進行GC操做,這一點和增量回收是類似的。不過,相對於在一個CPU上進行GC任務分割的增量回收來講,並行回收能夠利用多CPU的性能,儘量讓這些GC任務並行(同時)進行。因爲軟件運行和GC操做是同時進行的,所以就會遇到和增量回收相同的問題。爲了解決這個問題,並行回收也須要用寫屏障來對當前的狀態信息保持更新。不過,讓GC操做徹底並行,而一點都不影響原有程序的運行,是作不到的。所以在GC操做的某些特定階段,仍是須要暫停原有程序的運行。在多核化快速發展的如今,並行回收也成了一個很是重要的話題,它的算法也在不斷進行改善。在硬件系統的支持下,無需中斷原有程序的徹底並行回收器也已經呼之欲出。從此,這個領域至關值得期待。