1.前言
在讀這篇博客之前,你需要了解分代收集理論中,收集器應該將Java堆劃分出不同的區(qū)域**,**然后將回收對象依據(jù)其年齡(年齡即對象熬過垃圾收集過程的次數(shù))分配到不同的區(qū)域之中存儲。
例如appel式回收,HotSpot虛擬機中的新生代收集器都采用了appel式回收來設計新生代內(nèi)存布局。
Appel式回收的具體做法是把新生代分為一塊較大的Eden空間和兩塊較小的 Survivor空間,每次分配內(nèi)存只使用Eden和其中一塊Survivor。發(fā)生垃圾收集時,將Eden和Survivor中仍然存活的對象一次性復制到另外一塊Survivor空間上,然后直接清理掉Eden和已用過的那塊Survivor空間。HotSpot虛擬機默認Eden和Survivor的大小比例是8∶1,也即每次新生代中可用內(nèi)存空間為整個新 生代容量的90%(Eden的80%加上一個Survivor的10%),只有一個Survivor空間,即10%的新生代是會 被“浪費”的。當然,98%的對象可被回收僅僅是“普通場景”下測得的數(shù)據(jù),任何人都沒有辦法百分百 保證每次回收都只有不多于10%的對象存活,因此Appel式回收還有一個充當罕見情況的“逃生門”的安 全設計,當Survivor空間不足以容納一次Minor GC之后存活的對象時,就需要依賴其他內(nèi)存區(qū)域(實際上大多就是老年代)進行分配擔保(Handle Promotion)。
所謂分配擔保就是:如果另外一塊 Survivor空間沒有足夠空間存放上一次新生代收集下來的存活對象,這些對象便將通過分配擔保機制直 接進入老年代,這對虛擬機來說就是安全的。
在將Java堆內(nèi)存劃分為不同的區(qū)域之后,垃圾收集器才可以每次只回收其中某一個或者某些部分的區(qū)域(Minor GC、Major GC、Full GC)。進而演化出與對象存亡特征相匹配的垃圾收集算法。
2.正文
1.對象優(yōu)先在Eden區(qū)分配
大多數(shù)情況下,對象在新生代Eden區(qū)中分配,當Eden區(qū)沒有足夠空間進分配時,虛擬機將發(fā)起一次MinorGC
2.大對象直接進入老年代
大對象指的是需要大量連續(xù)內(nèi)存空間的對象,比如一個很長的字符串或者是一個很大的數(shù)組。
大對象對虛擬機的內(nèi)存分配來說就是一個不折不扣的壞消息,比遇到一個大對象更加壞的消息就是遇到一群“朝生夕滅”的“短命大對象”,我們寫程序的時候應注意避免。在Java虛擬機中要避免大對象的原因是,在分配空間時,它容易導致內(nèi)存明明還有不少空間時就提前觸發(fā)垃圾收集,以獲取足夠的連續(xù)空間才能安置好它們,而當復制對象時大對象就意味著高額的內(nèi)存復制開銷。
HotSpot虛擬機提供了 -XX:PretenureSizeThreshold
參數(shù),指定大于該設置值的對象直接在老年代分配,這樣做的目的就是避免在Eden區(qū)及兩個Survivor區(qū)之間來回復制,產(chǎn)生大量的內(nèi)存復制操作。
3.長期存活的對象將進入老年代
在前面我們了解了對象的創(chuàng)建過程中,會設置對象頭,其中對象頭中就包含了對象的年齡。
對象通常在Eden區(qū)里誕生,如果經(jīng)過第一次 Minor GC后仍然存活,并且能被Survivor容納的話,該對象會被移動到Survivor空間中,并且將其對象年齡設為1歲。對象在Survivor區(qū)中每熬過一次Minor GC,年齡就增加1歲,當它的年齡增加到一定程度(默認為15),就會被晉升到老年代中。
對象晉升老年代的年齡閾值,可以通過參數(shù)-XX: MaxTenuringThreshold
設置。
4.動態(tài)對象年齡判斷
為了能更好地適應不同程序的內(nèi)存狀況,HotSpot虛擬機并不是永遠要求對象的年齡必須達到 -XX:MaxTenuringThreshold 才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代,無須等到 -XX:MaxTenuringThreshold
中要求的年齡。
5.空間分配擔保
在發(fā)生Minor GC之前,虛擬機必須先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間,如果這個條件成立,那這一次Minor GC可以確保是安全的。如果不成立,則虛擬機會先查看 -XX:HandlePromotionFailure
參數(shù)的設置值是否允許擔保失?。℉andle Promotion Failure)。
-XX:HandlePromotionFailure=true
代表允許擔保失敗;-XX:HandlePromotionFailure=false
代表不允許擔保失敗
如果允許,那會繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小,如果大于,將嘗試進行一次Minor GC,盡管這次Minor GC是有風險的;如果小于,或者 -XX:HandlePromotionFailure
設置不允許冒險,那這時就要改為進行一次Full GC。
所謂冒險,在允許擔保失敗的情況下,將Survivor無法存放的存活對象,由老年代進行分配擔保,這個步驟是有一定風險的。因為新生代中會有多少對象將發(fā)生晉升(新生代—>老年代)我們無法知道。
- 如果老年代中有足夠的空間來容納新生代中所有對象(最壞的情況,所有新生代對象都晉升),那么這種情況相對來說安全的多。
- 如果是老年代沒有足夠的空間來容納新生代中所有對象,且我們無法知道有多少新生代對象發(fā)生晉升。簡單來說就是我們無法確定老年代是否有足夠的空間存放新生代中晉升的對象,我們只能以歷史平均晉升值作為參考,那么這種情況就可能會發(fā)生擔保失敗。
取歷史平均值來比較其實仍然是一種賭概率的解決辦法,也就是說假如某次Minor GC存活后的對象突增,遠遠高于歷史平均值的話,依然會導致?lián)J?。如果出現(xiàn)了擔保失敗,那就只好老老實實 地重新發(fā)起一次Full GC,這樣停頓時間就很長了。雖然擔保失敗時繞的圈子是最大的,但通常情況下都還是會將-XX:HandlePromotionFailure
開關(guān)打開,避免Full GC過于頻繁。
會將**-XX:HandlePromotionFailure
開關(guān)打開,避免Full GC過于頻繁。
注意:該參數(shù)JDK1.7以后就廢棄了, 只要老年代的連續(xù)空間大于新生代對象的總大小或者歷次晉升到老年代的對象的平均大小就進行MinorGC,否則Fu11GC文章來源:http://www.zghlxwxcb.cn/news/detail-439688.html
本文參考自《深入理解Java虛擬機》(第三版)文章來源地址http://www.zghlxwxcb.cn/news/detail-439688.html
到了這里,關(guān)于深入理解Java虛擬機——內(nèi)存分配與回收策略的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!