注:此文是我在讀完周志明老師的深入理解Java虛擬機之后總結的一篇文章嚷狞,請閱讀此書獲取更加詳細的信息.
判斷對象是否存活的算法
(1)引用計數算法:
每當一個地方引用一個對象時,計數器值就加一;當引用失效時嗜愈,計數器值就減一访得;任何時刻計數器為0的對象就是不可能再被使用的對象.
(2)可達性分析算法:
從"GC Roots"的對象作為起始點咸作,從這些節(jié)點開始向下搜索,搜索所走過的路徑稱為引用鏈酣衷,當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的.
可作為GC Roots的對象包括:
- 在虛擬機棧中引用的對象
- 本地方法棧中引用的對象
- 方法區(qū)中類靜態(tài)屬性引用的對象
- 方法區(qū)中常量引用的對象
引用
- 強引用:我們普遍使用的引用次泽,形如Object obj = new Object()這類穿仪,只要強引用還存在,垃圾回收器永遠不會回收掉被引用的對象
- 軟引用:用來描述一些還有用但并非必須的對象.對于軟引用關聯著的對象意荤,在系統(tǒng)將要發(fā)出內存溢出異常之前啊片,將會把這些對象列進回收范圍內進行第二次回收,如果回收后內存依舊不足才會拋出內存溢出錯誤
- 弱引用:被弱引用引用的對象玖像,在進行下一次垃圾回收時紫谷,無論內存是否充足,都會被回收掉
- 虛引用:虛引用不會對對象的生存時間構成影響捐寥,也無法通過虛引用來獲取一個對象實例.為一個對象設置虛引用關聯的唯一目的就是能在這個對象被垃圾回收器回收時收到一個系統(tǒng)通知.
在可達性分析算法中笤昨,不可達的對象一定會死亡嗎?
不一定會死亡,對象還是有機會進行自我拯救的握恳,采用如下算法:
if(對象在可達性分析算法中不可達) {
第一次標記并篩選此對象
if(此對象被判定為有必要執(zhí)行finalize()方法) {
將對象放置在一個叫做F-Queue的隊列中
由虛擬機自動建立的瞒窒,低優(yōu)先級的Finalizer線程去執(zhí)行它
if(對象在finalize()方法中重新與引用鏈中的對象建立關聯) {
在第二次標記時,被移除出**即將回收**的隊列
} else {
被回收
}
}
}
對象滿足什么條件才被判定為沒有必要執(zhí)行finalize()方法?
- ①對象并沒有覆蓋finalize()方法
- ②finalize()方法已被執(zhí)行過(所以對象只能進行一次自救)
另外乡洼,Finalizer線程并不會保證等待finalize()執(zhí)行結束崇裁,是為了防止finalizer()方法中有死循環(huán)等阻塞此線程.
方法區(qū)的回收
在HotSpot中,方法區(qū)指的是永久帶.
垃圾回收不僅發(fā)生在堆上束昵,方法區(qū)也會進行垃圾回收拔稳,但是方法區(qū)的回收率一般比較低.
方法區(qū)進行垃圾回收時,主要回收兩部分內容:廢棄常亮和無用類.
那么什么樣的類才是無用類呢?
- ①該類的所有實例已被回收
- ②該類的類加載器已被回收
- ③該類對應的java.lang.Class對象沒有在任何地方被引用锹雏,無法在任何地方通過反射訪問該類的方法
垃圾收集算法
(1).標記-清除算法:
缺點是:
- ①內存碎片化
- ②標記和清除的效率都不高
(2).復制算法:
將新生代按照8:1:1的比例劃分為一塊Eden區(qū)和兩塊Survivor區(qū)壳炎,每次進行垃圾收集時,都是將Eden區(qū)和Survivor區(qū)中的存活對象復制到另一塊Survivor區(qū)中,然后清空Edent區(qū)和第一塊Survivor區(qū).
缺點是:
①復制過程開銷大
-
②會改變對象的內存地址
優(yōu)點是:
不會產生內存碎片
所以適用于對象存活比率較低的情況.
另外匿辩,Survivor空間并不總是足夠的腰耙,當不夠時,需要讓老年代為其進行分配擔保.
(3).標記-整理算法:
與標記-清除算法基本相同铲球,但是在標記過程完成后挺庞,是將存活對象向一側移動,然后清除另一側的內存.
(4).分代算法:
即不同的代采用上面提到的不同算法.
對于新生代這種存活對象少稼病,回收效率高的代选侨,采用復制算法,而對于老年代這種對象存活率少的代然走,則采用標記-清除算法或者標記-整理算法.
垃圾收集器
Serial收集器
單線程垃圾收集器援制,只會使用一條線程完成GC,并且會Stop The World(即停止所有用戶線程).
是Client模式下JVM的默認新生代收集器
ParNew收集器
跟Serial收集器有異同點.
相同點在于芍瑞,都會Stop The World, 差別在于晨仑,ParNew收集器在進行垃圾收集時,會采用多線程.
ParNew收集器是Server模式下JVM的首選新生代收集器.
若老年代采用CMS收集器拆檬,則新生代技能采用Serial或者ParNew收集器.
當CPU少時洪己,性能并不一定比Serial收集器好,因為存在線程切換的開銷.
Parallel Scavenge收集器
與ParNew相同竟贯,都是采用復制算法以及多線程的新生代垃圾收集器.
它的關注點與其他收集器不同答捕,CMS等收集器的關注點是盡可能地縮短垃圾收集時,用戶線程的停頓時間屑那,而Parallel Scavenge收集器的目的則是達到一個可控制的吞吐量.
吞吐量=(用戶代碼的執(zhí)行時間) / (用戶代碼的執(zhí)行時間 + 垃圾收集時間)
適合在后臺運算而不需要太多交互的任務.
提供用于控制吞吐量的參數拱镐,設置的吞吐量越高,它就越有可能自動將新生代的空間縮谐旨省.
還提供了開啟自適應調節(jié)策略的參數痢站,開啟之后就無需手工指定新生代的大小,Eden區(qū)與Survivor區(qū)的比例选酗,晉升老年代對象大小等細節(jié)參數了阵难,虛擬機會根據收集到的監(jiān)控信息自動為我們調節(jié).
Serial Old收集器
單線程老年代收集器,給Client模式下的虛擬機使用.
Parallel Old收集器
只能與Parallel Scanvage配合使用芒填,在注重吞吐量以及CPU資源敏感的場合呜叫,可以考慮Parallel Scanvage + Parallel Old收集器.
CMS收集器
采用標記-清除算法的老年代收集器.
經歷下面的四個步驟:
- ①初始標記:標記一下GC Roots能直接關聯到的對象,會Stop The World.
- ②并發(fā)標記:采用可達性分析算法找出不可達對象
- ③重新標記:修正并發(fā)標記期間由于用戶線程而導致變動的那一部分記錄殿衰,也會Stop The World
- ④并發(fā)清理:清楚不可達對象
缺點:
- CMS收集器對CPU資源非常敏感朱庆,在并發(fā)階段,啟用的線程數為(CPU數量+3)/4,所以可能會導致應用程序變慢
- CMS無法處理浮動垃圾闷祥,所以需要預留足夠的內存空間給用戶線程使用
- 會產生內存碎片娱颊,但是CMS中已經提供了對應的參數來優(yōu)化這個問題.
G1收集器
G1收集器的處理過程跟CMS收集器差不多傲诵,還是下面的四個階段:
- 初始標記:僅僅標記一下GC Roots能直接關聯到的對象,并且修改TAMS(Next Top at Mark Start)的值箱硕,讓下一階段用戶程序并發(fā)運行時拴竹,能在正確可用的Region中創(chuàng)建對象,這階段需要停頓線程剧罩,但是耗時很短.
- 并發(fā)標記:從GC Root開始對堆中對象進行可達性分析栓拜,找出存活對象,這階段耗時最長惠昔,但可與用戶程序并發(fā)執(zhí)行
- 最終標記:修正在并發(fā)標記階段因用戶程序繼續(xù)運行而導致標記產生變動的那一部分標記記錄幕与,可并行執(zhí)行
- 篩選回收:對各個Region的回收價值和成本進行排序,根據用戶所期望的GC停頓時間來制定回收計劃
G1和CMS的區(qū)別在哪里呢?
G1可以不依靠其他的垃圾回收器就獨立管理整個堆.在G1之前的其他收集器進行收集的范圍都是整個新生代或者老年代镇防,而G1不再是這樣.使用G1收集器時啦鸣,Java堆的內存布局就與其他收集器有很大區(qū)別,它將整個Java堆劃分為多個大小相等的獨立區(qū)域来氧,雖然還保留著新生代和老年代的概念诫给,但新生代和老年代不再是物理隔離的了,它們都是一部分Region的集合.
G1和CMS的"標記-清理算法"不同饲漾,G1從整體上來看是基于"標記-整理算法"蝙搔,從局部來看是基于"復制算法"缕溉,這意味著G1運行時不會產生內存碎片.
G1能夠預測進行垃圾回收時停頓的時間考传,還能夠讓開發(fā)者明確指定垃圾回收的時間,消耗在垃圾回收上的時間不得超過這個時間.
G1能夠實現這點是因為能夠有計劃的避免在整個Java堆中進行全區(qū)域的垃圾收集.G1跟蹤各個Region里面的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需要的經驗值)证鸥,在后臺維護一個優(yōu)先列表僚楞,每次根據允許的收集時間,優(yōu)先回收價值最大的Region.這種使用Region劃分內存空間以及有優(yōu)先級的區(qū)域回收方式枉层,保證了G1收集器在有限的時間內可以獲取盡可能高的收集效率.
垃圾回收器參數總結
- UseSerialGC:虛擬機運行在Client模式下的默認值泉褐,打開此開關后,使用Serial+Serial Old的收集器組合進行內存回收.
- UseParNewGC:打開此開關后鸟蜡,使用ParNew + Serial Old的收集器組合進行內存回收
- UseConcMarkSweepGC:打開此開關后膜赃,使用ParNew + CMS + Serial Old的收集器組合進行內存回收.Serial Old收集器將作為CMS收集器出現Concurrent Mode Failure失敗后的后備收集器使用
- UseParallelGC:虛擬機運行在Server模式下的默認值,打開此開關后揉忘,使用Parallel Scavenge + Serial Old(PS MarkSweep)的收集器組合進行內存回收
- UseParallelOldGC:打開此開關后跳座,使用Parallel Scavenge + Parallel Old的收集器組合進行內存回收
- SurvivorRatio:新生代中Eden區(qū)與Survivor區(qū)的容量比值,默認為8,代表Eden:Survivor=8:1
- PretenureSizeThreshold:直接晉升到老年代的對象大小泣矛,設置這個參數后疲眷,大于這個參數的對象將直接在老年代進行分配
- MaxTenuringThreshold:晉升到老年代的對象年齡.每個對象在堅持過一次MinorGC之后,年齡就增加1您朽,當超過這個數值時狂丝,就進入老年代
- UseAdaptiveSizePolicy:動態(tài)調整Java堆中各個區(qū)域的大小以及進入老年代的年齡
- HandlePromotionFailure:動態(tài)允許分配擔保失敗,即老年代的剩余空間不足以應付新生代的整個Eden和Survivor區(qū)的所有對象都存活的極端情況
- ParalleleGCThreads:設置并行GC時進行垃圾回收的線程數
- GCTimeRatio:GC時間占總時間的比率,默認值為99几颜,即允許1%的GC時間.僅在使用Parallel Scavenge收集器時生效
- MaxGCPauseMillis:設置GC的最大停頓時間.僅在使用Parallel Scavenge收集器時生效.
- CMSInitiatingOccupancyFraction:設置CMS收集器在老年代空間被使用多少后觸發(fā)垃圾收集.默認值為68%,僅在使用CMS收集器時生效
- UseCMSCompactAtFullCollection:設置CMS收集器在完成垃圾收集后是否要進行一次內存碎片整理.僅在使用CMS收集器時生效
- CMSFullGCsBeforeCompaction:設置CMS收集器在進行若干次垃圾回收后再啟動一次內存碎片整理.僅在使用CMS收集器時生效.
內存分配與回收策略
對象主要分配在新生代的Eden區(qū)中倍试,如果啟動了本地線程分配緩沖,將按線程優(yōu)先在TLAB上分配.少數情況下也可能直接分配在老年代中菠剩,分配的規(guī)則并不是百分之百固定的易猫,其細節(jié)取決于當前使用的是哪一種垃圾回收器組合,還有虛擬機中與內存相關的參數的設置.
新生代的可用空間為Eden區(qū)+1個Survivor區(qū)的總容量.
大小超過-XX:PretenureSizeThreshold參數設定的尺寸的對象具壮,將直接在老年代中分配.
如果對象在Eden出生并經過第一次MinorGC后仍然存活准颓,并且能被Survivor容納的話,將被移動到Survivor空間中棺妓,并且對象年齡設為1.對象在Survivor區(qū)中每"熬過"一次Minor GC,年齡就增加1歲攘已,當他的年齡增加到一定程度(默認為15歲),就將會被晉升到老年代中.對象晉升老年代的年齡閾值怜跑,可以通過-XX:MaxTenuringThreshold參數設置.
為了能更好的適應不同程序的內存狀況样勃,虛擬機并不是永遠要求對象的年齡必須達到MaxTenuringThreshold才能晉級入老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半性芬,年齡大于或等于該年齡的對象就可以直接進入老年代峡眶,無須等到MaxTenuringThreshold所要求的年齡.
在發(fā)生Minor GC之前,虛擬機會先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間植锉,如果這個條件成立辫樱,那么Minor GC可以確保是安全的.如果不成立,則虛擬機會查看HandlePromotionFailure設置值是否允許擔保失斂”印.如果允許狮暑,那么會繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小,如果大于辉饱,將嘗試著進行一次Minor GC,盡管這次Minor GC是有風險的搬男;如果小于,或者HandlePromotionFailure設置為不允許冒險彭沼,那這時也要改為進行一次Full GC.