一、GC 第一步:判斷對象是否已死?有兩種方法:第一種是引用計數法,即給對象添加一個引用計數器,當被引用時,計數器就+1;當引用失效時,就-1;當計數器為0時,代表對象沒有被引用。但是計數器的缺點就是:對象之間相互引用時導致計數器不為零,無法被回收。第二種方法是可達性分析法,即通過定義一系列的GC ...
一、GC
第一步:判斷對象是否已死?有兩種方法:第一種是引用計數法,即給對象添加一個引用計數器,當被引用時,計數器就+1;當引用失效時,就-1;當計數器為0時,代表對象沒有被引用。但是計數器的缺點就是:對象之間相互引用時導致計數器不為零,無法被回收。第二種方法是可達性分析法,即通過定義一系列的GC Roots對象作為起始點,從這些起點向下搜索,當一個對象到GC Roots沒有任何引用鏈時,則此對象是不可用的。
可以作為GC Roots的對象包括:虛擬機棧中引用的對象、方法去種類靜態屬性或常量引用的對象,本地方法棧中native方法引用的對象。
這裡再說明一下引用!!!
引用分為:強引用:把一個對象賦給一個引用變數,這個引用變數就是強引用。(強引用的對象不會被回收)
軟引用:用來描述一些還有用但非必需的對象。(在系統將要發生溢出異常之前,將這些對象列入回收範圍進行二次回收,如果回收之後還沒有足夠記憶體,才會拋出異常。利用SoftReference類實現軟引用)
弱引用:用來描述非必需的對象,它的強度較軟引用弱。(當發生GC時,無論當前記憶體是否足夠,都會回收被弱引用關聯的對象。利用WeakReference類來實現弱引用)
虛引用:主要作用是跟蹤對象被垃圾回收的狀態,是最弱的引用關係。(PhantomReference類來實現虛引用)
一個對象的死亡,至少要經歷兩次標記的過程:如果對象在進行可達性分析後發現沒有與GC Roots相連的引用鏈,那它將會被第一次標記並且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。如果對象沒有覆蓋finalize()方法或者finalize方法已經被虛擬機調用過,則沒有必要執行。如果這個對象被判定為有必要執行finalize方法,那麼這個對象將會放置在一個叫做F-Queue的隊列中,併在稍後由虛擬機自動建立一個低優先順序的Finalizer線程去執行它。
finalize方法是對象逃脫死亡命運的最後一次機會,稍後GC將對F-Queue中的對象進行第二次標記,如果對象重新與引用鏈的任何一個對象建立關聯,那在第二次標記時會將它移除“即將回收”的集合;如果對象沒有逃脫,那麼它就真的被回收了。
回收方法區:永久帶的垃圾收集主要回收廢棄常量和無用的類。判斷一個類是不是“無用的類”條件:該類所有的實例都已經被回收;載入該類的ClassLoader已經被回收;該類對應的java.lang.Class對象沒有在任何地方被引用,無法通過反射訪問該類方法。
第二步:垃圾收集演算法
第一種是標記-清除演算法(mark-sweep),演算法分為標記和清除兩部分:首先標記出所有要回收的對象,標記完成後統一回收所有被標記的對象。缺點:一個是標記和清除兩個過程效率不高,另一個是標記和清除之後產生大量不連續的空間碎片,導致以後分配較大對象時找不到足夠的記憶體而提前觸發GC。
第二種是複製演算法(copying),它是將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中一塊。當這一塊記憶體用完後,就將還存活的對象複製到另一塊上面,然後再把已使用過的記憶體一次清理掉。優點:解決了記憶體碎片化,實現簡單,運行高效;缺點:將記憶體縮小為原來的一半,這樣代價較大。
第三種是標記-整理演算法(mark-compact),分為標記和整理兩部分,首先標記出所有要回收的對象,將所有存活的對象都向一端移動,然後直接清理掉端邊以外的記憶體。
第四種是分代收集演算法,就是根據對象存貨周期的不同將記憶體劃分為新生代和老年代,然後根據每個年代特性採用合適的演算法。在新生代中每次都有大量對象死去,只有少量存活,那麼就採用複製演算法;而老年代對象存活率高沒有額外空間對它進行擔保,就必須使用標記-清除或標記-整理演算法來回收。
第三步:垃圾收集器
Serial收集器,是單線程收集器,它進行GC時必須暫停其他所有的工作線程,直到它收集結束,這項工作實際是虛擬機在後臺自動發起和自動完成的。
ParNew收集器,就是Serial收集器的多線程版本,除了使用多線程進行垃圾收集之外,其餘行為與Serial收集器一樣。
Parallel Scavenge收集器,是使用複製演算法的新生代收集器,又是並行的多線程收集器。它的目的是達到一個可控制的吞吐量,高效率地利用CPU時間, PS收集器提供了控制最大垃圾收集停頓時間的 -XX:MaxGCPauseMillis參數和直接設置吞吐量大小的 -XX:GCTimeRatio參數用於精確控制吞吐量。(PS收集器還有一個參數-XX:+UseAdaptiveSizePolicy,這是一個開關參數,這個開關打開後,就不用手工指定新生代的大小、Eden和Survivor區的比例、晉升老年代對象的大小等細節參數,虛擬機會根據當前系統的運行情況收集性能監控信息動態調整這些參數,這種調節方式稱為GC自適應的調節機制)。
Serial Old收集器,是單線程收集器,使用標記-整理演算法,是Serial收集器的老年代版本,是給Client模式下的虛擬機使用。
Paralled Old收集器,是Parallel Scavenge收集器的老年代版本,使用多線程和標記-整理演算法。
CMS收集器,是一種以獲取最短回收停頓時間為目標的收集器,使用多線程和標記-清楚演算法。
運作過程分為:初始標記(僅僅只是標記一下GC Roots能直接關聯到的對象)
併發標記(進行GC Roots Tracing 的過程)
重新標記(為了修正併發標記期間因用戶程式繼續運作而導致標記產生變動的那一部分標記記錄)
併發清除(清除GC Roots不可達的對象,不需要暫停工作線程)。
優點:併發收集、低停頓;
缺點:CMS收集器對CPU資源非常敏感、CMS收集器無法處理浮動垃圾、基於標記-清除演算法會產生大量的空間碎片。
G1收集器,是一款面向服務端的垃圾收集器,G1具備並行與併發、分代收集、空間整合(基於標記-整理和複製演算法實現,不會產生空間碎片)、可預測停頓的特點;G1收集器中它將Java堆劃分為多個大小相等的獨立區域,獨立使用和回收,這樣可以控制一次回收多個區域,減少一次GC所產生的停頓(區域劃分機制)。G1之所以能建立可預測的停頓模型,是因為G1跟蹤各個區域裡面的垃圾堆積的價值大小,在後臺維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的區域(優先區域回收機制)。為了避免全堆掃描,G1使用Remembered Set來管理相關的對象引用信息。當進行記憶體回收時,在GC根節點的枚舉範圍中加入Remembered Set即可保證不對全堆掃描也不會有遺漏。
運作過程分為:初始標記(僅僅只是標記一下GC Roots能直接關聯的對象,並修改TAMS的值,讓下一階段用戶程式併發運行時,能在正確可用的region中創建對象,此階段需要停頓線程,但時間很短)
併發標記(從GC Root開始對堆中的對象進行可達性分析,找出存活的對象,耗時長,但可與用戶線程併發執行)
最終標記(為了修正併發標記期間因用戶程式繼續運行而導致標記產生變動的那一部分標記記錄並把Remembered Set Logs的數據合併到Remembered Set 中,此階段需要停頓線程,但可併發執行)
篩選回收(首先根據各個region的回收價值和成本進行排序,根據用戶所期望的GC停頓時間來制定回收計劃)
並行(Parallel):指多條垃圾收集線程並行工作,但此時用戶線程仍然處於等待狀態。
併發(Concurrent):指用戶線程與垃圾收集線程同時執行。
二、記憶體分配與回收策略
大多數情況下,對象分配在新生代的Eden區上,分配規則是不固定的,取決於當前使用的垃圾收集器以及虛擬機中與記憶體相關的參數設置。
新生代:用來存放新產生的對象,占用1/3空間;新生代分為一個Eden區和兩個Survivor區(FromSurvivor和ToSurvivor);當Eden區沒有足夠空間進行分配時,虛擬機將會進行一次Minor GC。進行一次Minor GC後的對象進入FromSurvivor區,當From和Eden區沒有可用空間時,會進行第二次Minor GC,將存活的對象移到ToSurvivor區。
大對象(需要大量連續記憶體空間的對象)直接進入老年代;虛擬機提供了一個-XX:PretenureSizeThreshold參數,令大於這個設置值的對象直接在老年代分配,這樣做避免了在Eden區及兩個Survivor區之間發生大量的記憶體複製。長期存活的對象將進入老年代,如果對象在Eden區出生並且經過第一次Minor GC後仍然存活,將被移動到Survivor區,並且對象年齡設為1。對象在Survivor區中每經過一次Minor GC,年齡就+1,當它的年齡增加到一定程度時(預設為15),就將會被晉升到老年代中。對象晉升老年代的年齡閾值,可以通過參數-XX:MaxTenuringThreshold設置。
動態對象年齡判定:如果在Survivor區中相同年齡的所有對象大小總和大於Survivor區的一半,年齡大於或等於該年齡的對象可以直接進入老年代。
空間分配擔保:當出現大量對象在Minor GC後仍然存活就需要老年代進行分配擔保,把Survivor區無法容納的對象直接進入老年代中。前提是老年代本身有容納這些對象的剩餘空間。
在發生Minor GC之前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代所有對象總大小或者歷次晉升到老年代對象的平均大小,如果大於就進行MinorGC,否則將進行FullGC。