JVM垃圾收集器與內存分配策略

判斷對象是否存活
在進行GC之前,首先要確定的就是在java堆中那些對象已經“死去”那些對象還“活着”
引用記數法(Referencecounting)
瞭解即可,虛擬機並不是通過該算法來判斷對象是否存活。
引用計數器的實現很簡單,對於一個對象A,只要有任何一個對象引用了A,則A的引用計數器就加1,當引用失效時,引用計數器就減1。只要對象A的引用計數器的值爲0,則對象A就不可能再被使用。
存在的問題:很難處理對象的循環引用。根對象已經不可達,垃圾對象的相互引用造成垃圾對象的引用計數都不爲1,所以就不會被回收。
可達性分析算法(ReachabilityAnalysis)
主流虛擬機通過該算法來判斷對象是否存活。
GCRoots(一組必須活躍的引用對象)的對象爲起點,如果從GCroots到這個對象不可達,就說明該對象是不可用的,被判定爲可回收的對象
可作爲GCRoots的對象:
虛擬機棧中應用的對象,方法區中類靜態屬性應用的對象,方法區中常量引用的對象,Native方法應用的對象。
對象的自救
在可達性分析算法中“不可達”的對象,並不是“非死不可的”。暫時處於“緩刑”。
一個對象如果被標記爲“不可達”,接下來將會進行篩選,即判斷該對象是否有必要執行fianlize方法,如果對象中沒有覆蓋finalize方法,或者finalize方法已經被JVM調用過了(任何一個對象的finalize方法只會被系統自動調用一次)。則判定沒有必要執行finalize方法。判定對象死亡。
finalize方法是對象逃脫死亡的最後一次機會。
如果對象被判定爲有必要執行finalize方法,只要在finalize方法中重新與引用鏈上任意一個對象建立關聯(比如把自己(this關鍵字)賦值給一個變量)。對象就會自救成功。
 
垃圾收集算法
標記—清除算法(Mark-Sweep)
標記-清除算法是現代垃圾回收算法的思想基礎。標記-清除算法將垃圾回收分爲兩個階段:標記階段和清除階段。一種可行的實現是,在標記階段,首先通過根節點,標記所有從根節點開始的可達對象。因此,未被標記的對象就是未被引用的垃圾對象。然後,在清除階段,清除所有未被標記的對象。

該算法有兩個不足:
一是效率,標記和清除兩個階段效率都不高。
二是空間問題,標記清除後會產生大量不連續的內存碎片,這樣以後在位大對象分配空間時,由於找不到足夠的連續內存會提前出發下一次GC
標記—整理算法(MarkCompact)
標記-整理算法適合用於存活對象較多的場合,如老年代。它在標記-清除算法的基礎上做了一些優化。
和標記-清除算法一樣,標記-壓縮算法也首先需要從根節點開始,對所有可達對象做一次標記。但之後,它並不簡單的清理未標記的對象,而是將所有的存活對象移動到內存的一端。之後,清理邊界外所有的空間。

複製算法(Copying)
與標記-清除算法相比,複製算法是一種相對高效的回收方法。
不適用於存活對象較多的場合,如老年代。
將原有的內存空間分爲兩塊,每次只使用其中一塊,在垃圾回收時,將正在使用的內存中的存活對象複製到未使用的內存塊中,之後,清除正在使用的內存塊中的所有對象,交換兩個內存的角色,完成垃圾回收。

 
JavaGC、新生代、老年代
Java中的堆是JVM所管理的最大的一塊內存空間,主要用於存放各種類的實例對象。
Java中,堆被劃分成兩個不同的區域:新生代(Young)、老年代(Old)。新生代(Young)又被劃分爲
三個區域:EdenFrom SurvivorTo Survivor
這樣劃分的目的是爲了使JVM能夠更好的管理堆內存中的對象,包括內存的分配以及回收。

 
從圖中可以看出:堆大小=新生代+老年代。其中,堆的大小可以通過參數–Xms-Xmx來指定。
默認的,新生代(Young)與老年代(Old)的比例的值爲1:2(該值可以通過參數–XX:NewRatio來指定,即:新生代(Young)=1/3的堆空間大小。老年代(Old)=2/3的堆空間大小。其中,新生代(Young)被細分爲Eden和兩個Survivor區域,這兩個Survivor區域分別被命名爲fromto,以示區分。
默認的,Eden:from:to=8:1:1(可以通過參數–XX:SurvivorRatio來設定),即:Eden=8/10的新生代空間大小,from=to=1/10的新生代空間大小。
JVM每次只會使用Eden和其中的一塊Survivor區域來爲對象服務,所以無論什麼時候,總是有一塊Survivor區域是空閒着的。因此,新生代實際可用的內存空間爲9/10(90%)的新生代空間。

 
分代收集算法(Generational Colletion)
依據對象的存活週期進行分類,短命對象歸爲新生代,長命對象歸爲老年代。
根據不同代的特點,選取合適的收集算法。
少量對象存活,適合複製算法
大量對象存活,適合標記清理或者標記整理算法。
內存分配及回收策略
對象優先在Eden(伊甸園)分配:大多數情況下對戲在新生代Eden中分配,當Eden區中沒有足夠的空間時,虛擬機將發起一次Minor GC
大對象直接進入老年代:所謂大對象就是指那些需要大量連續內存空間的Java對象,最典型的大對象就是那些很長的字符串以及數組。
長期存活的進入老年代:虛擬機給每個對象都定義了一個年齡計數器。對象在Eden出生,經過一次Minor GC還存活。對象的年齡就加一,當它的年齡增加到一定程度時(默認是15),就會被晉升到老年代。
 
新生代GC(Minor GC):指發生在新生代的GC動作,因爲大多數Java對象都具有朝生夕滅的特點,所以Minor GC發生較爲頻繁,回收速度頁比較快。
 
老年代GC(Major GCFull GC):指發生在老年代的GC,出現了Major GC,至少會伴隨一次Minor GCMajor GC的速度比Minor GC慢10倍以上。
 
內存溢出和內存泄漏
內存溢出out of memory(memory overflow),是指程序在申請內存時,沒有足夠的內存空間供其使用,出現out of memory.
泄漏,什麼是泄漏?我舉個簡單的例子,不知道是不是這個意思,就比如說有人跟你關係不錯,找你借了點錢,但是後來他搬家了,新地址你不知道,你想找他要錢回來,但是就是找不到他在什麼地方。專業點的話就是說你向系統申請到了你想要的內存空間,但是使用完了之後卻不歸還,結果你申請到的內存空間你自己也訪問不到(也許你把地址搞丟了),系統也無法分配該空間給其他的程序。這就是一次泄漏.
內存泄露是指無用對象(不再使用的對象)持續佔有內存或無用對象的內存得不到及時釋放,從而造成的內存空間的浪費稱爲內存泄露。內存泄露的堆積會造成內存溢出。
Java內存泄露根本原因是什麼呢長生命週期的對象持有短生命週期對象的引用就很可能發生內存泄露,儘管短生命週期對象已經不再需要,但是因爲長生命週期對象持有它的引用而導致不能被回收,這就是java中內存泄露的發生場景
 
造成的原因有:1、靜態集合類引起內存泄漏:2、當集合裏面的對象屬性被修改後,再調用remove()方法時不起作用。3、監聽器。4、各種連接。5、內部類和外部模塊的引用。
垃圾收集器
CMS收集器
CMS收集器是一種以獲取最短回收停頓時間爲目標的收集器。基於標記-清除”算法實現,它的運作過程如下:
1)初始標記(CMS initial mark)
2)併發標記(CMS concurrent mark)
3)重新標記(CMS remark)
4)併發清除(CMS concurrent sweep)
初始標記、重新標記這兩個步驟仍然需要stop the world”,初始標記僅僅只是標記一下GC Roots能直接關聯到的對象,速度很快,
併發標記階段就是進行GC Roots Tracing的過程
而重新標記階段則是爲了修正併發標記期間因用戶程序繼續運作而導致標記產生表動的那一部分對象的標記記錄,這個階段的停頓時間一般會比初始標記階段稍長點,但遠比並發標記的時間短。
在整個過程中耗時最長的的併發標記和併發清除過程收集器線程都可以和用戶線程一起工作,所以從總體上說,CMS收集器的內存回收過程是與用戶線程一起併發執行的。

 
CMS是一款優秀的收集器,主要優點:併發收集、低停頓
缺點:
1CMS收集器對CPU資源非常敏感。在併發階段,它雖然不會導致用戶線程停頓,但是會因爲佔用了一部分線程而導致應用程序變慢,總吞吐量會降低。
2CMS收集器無法處理浮動垃圾,可能會出現“Concurrent Mode Failure(併發模式故障)”失敗而導致另一次Full GC產生。
浮動垃圾(Floating Garbage:由於CMS併發清理階段用戶線程還在運行着,伴隨着程序運行自然就會有新的垃圾不斷產生,這部分垃圾出現的標記過程之後,CMS無法在當次收集中處理掉它們,只好留待下一次GC中再清理。這些垃圾就是“浮動垃圾”。
3CMS是一款“標記--清除”算法實現的收集器,容易出現大量空間碎片。當空間碎片過多,將會給大對象分配帶來很大的麻煩,往往會出現老年代還有很大空間剩餘,但是無法找到足夠大的連續空間來分配當前對象,不得不提前觸發一次Full GC
G1收集器
G1(Garbage-First)收集器是當今集線器技術發展的最前沿成果之一。G1是一款面向服務端應用的垃圾收集器。G1具備如下特點:
1、並行與併發:G1能充分利用CPU、多核環境下的硬件優勢,使用多個CPUCPU或者CPU核心)來縮短stop-The-World停頓時間。部分其他收集器原本需要停頓Java線程執行的GC動作,G1收集器仍然可以通過併發的方式讓java程序繼續執行。
2、分代收集:雖然G1可以不需要其他收集器配合就能獨立管理整個GC堆,但是還是保留了分代的概念。它能夠採用不同的方式去處理新創建的對象和已經存活了一段時間,熬過多次GC的舊對象以獲取更好的收集效果。
3、空間整合:與CMS的“標記--清理”算法不同,G1從整體來看是基於“標記整理”算法實現的收集器;從局部上來看是基於“複製”算法實現的。
4、可預測的停頓:這是G1相對於CMS的另一個大優勢,降低停頓時間是G1和CMS共同的關注點,但G1除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度爲M毫秒的時間片段內,
 
G1收集器的運行步驟
1)初始標記(CMS initial mark)
2)併發標記(CMS concurrent mark)
3)最終標記(Final Marking)
4)篩選回收(Live Data Counting and Evacuation)
上面幾個步驟的運作過程和CMS有很多相似之處。初始標記階段僅僅只是標記一下GC Roots能直接關聯到的對象,並且修改TAMS的值,讓下一個階段用戶程序併發運行時,能在正確可用的Region中創建新對象,這一階段需要停頓線程,但是耗時很短,併發標記階段是從GC Root開始對堆中對象進行可達性分析,找出存活的對象,這階段時耗時較長,但可與用戶程序併發執行。而最終標記階段則是爲了修正在併發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分標記記錄,虛擬機將這段時間對象變化記錄在線程Remenbered Set Logs裏面,最終標記階段需要把Remembered Set Logs的數據合併到Remembered Set Logs裏面,最終標記階段需要把Remembered Set Logs的數據合併到Remembered Set中,這一階段需要停頓線程,但是可並行執行。最後在篩選回收階段首先對各個Region的回收價值和成本進行排序,根據用戶所期望的GC停頓時間來制定回收計劃。


發佈了24 篇原創文章 · 獲贊 9 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章