來源:pengjiaheng算法
網址:http://pengjiaheng.iteye.com/blog/548472併發
傳統分代垃圾回收方式,已經在必定程度上把垃圾回收給應用帶來的負擔降到了最小,把應用的吞吐量推到了一個極限。可是他沒法解決的一個問題,就是Full GC所帶來的應用暫停。在一些對實時性要求很高的應用場景下,GC暫停所帶來的請求堆積和請求失敗是沒法接受的。這類應用可能要求請求的返回時間在幾百甚至幾十毫秒之內,若是分代垃圾回收方式要達到這個指標,只能把最大堆的設置限制在一個相對較小範圍內,可是這樣有限制了應用自己的處理能力,一樣也是不可接收的。jvm
分代垃圾回收方式確實也考慮了實時性要求而提供了併發回收器,支持最大暫停時間的設置,可是受限於分代垃圾回收的內存劃分模型,其效果也不是很理想。性能
爲了達到實時性的要求(其實Java語言最初的設計也是在嵌入式系統上的),一種新垃圾回收方式呼之欲出,它既支持短的暫停時間,又支持大的內存空間分配。能夠很好的解決傳統分代方式帶來的問題。spa
增量收集的方式在理論上能夠解決傳統分代方式帶來的問題。增量收集把對堆空間劃分紅一系列內存塊,使用時,先使用其中一部分(不會所有用完),垃圾收集時把以前用掉的部分中的存活對象再放到後面沒有用的空間中,這樣能夠實現一直邊使用邊收集的效果,避免了傳統分代方式整個使用完了再暫停的回收的狀況。線程
固然,傳統分代收集方式也提供了併發收集,可是他有一個很致命的地方,就是把整個堆作爲一個內存塊,這樣一方面會形成碎片(沒法壓縮),另外一方面他的每次收集都是對整個堆的收集,沒法進行選擇,在暫停時間的控制上仍是很弱。而增量方式,經過內存空間的分塊,偏偏能夠解決上面問題。設計
這部分的內容主要參考這裏,這篇文章算是對G1算法論文的解讀。我也沒加什麼東西了。rest
目標對象
從設計目標看G1徹底是爲了大型應用而準備的。blog
支持很大的堆
高吞吐量
–支持多CPU和垃圾回收線程
–在主線程暫停的狀況下,使用並行收集
–在主線程運行的狀況下,使用併發收集
實時目標:可配置在N毫秒內最多隻佔用M毫秒的時間進行垃圾回收
固然G1要達到實時性的要求,相對傳統的分代回收算法,在性能上會有一些損失。
算法詳解
G1可謂博採衆家之長,力求到達一種完美。他吸收了增量收集優勢,把整個堆劃分爲一個一個等大小的區域(region)。內存的回收和劃分都以region爲單位;同時,他也吸收了CMS的特色,把這個垃圾回收過程分爲幾個階段,分散一個垃圾回收過程;並且,G1也認同分代垃圾回收的思想,認爲不一樣對象的生命週期不一樣,能夠採起不一樣收集方式,所以,它也支持分代的垃圾回收。爲了達到對回收時間的可預計性,G1在掃描了region之後,對其中的活躍對象的大小進行排序,首先會收集那些活躍對象小的region,以便快速回收空間(要複製的活躍對象少了),由於活躍對象小,裏面能夠認爲多數都是垃圾,因此這種方式被稱爲Garbage First(G1)的垃圾回收算法,即:垃圾優先的回收。
回收步驟:
初始標記(Initial Marking)
G1對於每一個region都保存了兩個標識用的bitmap,一個爲previous marking bitmap,一個爲next marking bitmap,bitmap中包含了一個bit的地址信息來指向對象的起始點。
開始Initial Marking以前,首先併發的清空next marking bitmap,而後中止全部應用線程,並掃描標識出每一個region中root可直接訪問到的對象,將region中top的值放入next top at mark start(TAMS)中,以後恢復全部應用線程。
觸發這個步驟執行的條件爲:
G1定義了一個JVM Heap大小的百分比的閥值,稱爲h,另外還有一個H,H的值爲(1-h)*Heap Size,目前這個h的值是固定的,後續G1也許會將其改成動態的,根據jvm的運行狀況來動態的調整,在分代方式下,G1還定義了一個u以及soft limit,soft limit的值爲H-u*Heap Size,當Heap中使用的內存超過了soft limit值時,就會在一次clean up執行完畢後在應用容許的GC暫停時間範圍內儘快的執行此步驟;
在pure方式下,G1將marking與clean up組成一個環,以便clean up能充分的使用marking的信息,當clean up開始回收時,首先回收可以帶來最多內存空間的regions,當通過屢次的clean up,回收到沒多少空間的regions時,G1從新初始化一個新的marking與clean up構成的環。
併發標記(Concurrent Marking)
按照以前Initial Marking掃描到的對象進行遍歷,以識別這些對象的下層對象的活躍狀態,對於在此期間應用線程併發修改的對象的以來關係則記錄到remembered set logs中,新建立的對象則放入比top值更高的地址區間中,這些新建立的對象默認狀態即爲活躍的,同時修改top值。
最終標記暫停(Final Marking Pause)
當應用線程的remembered set logs未滿時,是不會放入filled RS buffers中的,在這樣的狀況下,這些remebered set logs中記錄的card的修改就會被更新了,所以須要這一步,這一步要作的就是把應用線程中存在的remembered set logs的內容進行處理,並相應的修改remembered sets,這一步須要暫停應用,並行的運行。
存活對象計算及清除(Live Data Counting and Cleanup)
值得注意的是,在G1中,並非說Final Marking Pause執行完了,就確定執行Cleanup這步的,因爲這步須要暫停應用,G1爲了可以達到準實時的要求,須要根據用戶指定的最大的GC形成的暫停時間來合理的規劃何時執行Cleanup,另外還有幾種狀況也是會觸發這個步驟的執行的:
G1採用的是複製方法來進行收集,必須保證每次的」to space」的空間都是夠的,所以G1採起的策略是當已經使用的內存空間達到了H時,就執行Cleanup這個步驟;
對於full-young和partially-young的分代模式的G1而言,則還有狀況會觸發Cleanup的執行,full-young模式下,G1根據應用可接受的暫停時間、回收young regions須要消耗的時間來估算出一個yound regions的數量值,當JVM中分配對象的young regions的數量達到此值時,Cleanup就會執行;partially-young模式下,則會盡可能頻繁的在應用可接受的暫停時間範圍內執行Cleanup,並最大限度的去執行non-young regions的Cleanup。
之後JVM的調優或許跟多須要針對G1算法進行調優了。