深入理解java虛擬機學習筆記(二) jvm垃圾收集器和內(nèi)存分配策略

1.垃圾如何確認

對于大多數(shù)語言中判斷對象是否存活會采用引用計數(shù)法:給對象添加一個引用計數(shù)器,當有一個地方引用時,計數(shù)器就加1,當引用失效時,計數(shù)器就減1。任何時刻只要計數(shù)器為0則回收。但是這種算法無法解決對象之間互相循環(huán)引用的問題。如A引用B,而B又引用A,計數(shù)器永遠不為0,這兩個對象再也無任何引用。這樣GC不能回收這兩個對象。
因此,在JAVA中,采用了可達性分析算法來解決這個問題,判斷對象是否存活。
可達性分析算法:通過GCRoots的對象作為起點,從這些節(jié)點向下搜索,搜索走過的路徑稱之為引用鏈(Reference Chain),當一個對象到達GCRoots沒有任何鏈相連,則證明此對象不可用,可以被GC回收。

可達性分析算法

上圖藍色部分將會被GC回收。

2.垃圾收集算法

2.1 標記-清除算法

標記-清除算法是最基礎的垃圾收集算法。分為標記和清除兩個階段:
首先標記出需要回收的對象,在標記完成后統(tǒng)一回收所有被標記的對象。
存在的問題: 一是效率低,標記和清除兩個過程效率都不高。二是空間問題,標記清除后會產(chǎn)生大量的不連續(xù)的內(nèi)存碎片??臻g碎片太多會導致程序在運行過程中需要分配較大對象時無法找到連續(xù)內(nèi)存而不得不提前觸發(fā)GC。

2.2復制算法

為了解決效率問題,復制算法應運而生。它將可用內(nèi)存分為大小相等的兩塊,每次只使用其中一塊,當其中一塊內(nèi)存耗盡,觸發(fā)GC時就將還存在的對象復制到另外一塊內(nèi)存上面,然后再把已使用過的內(nèi)存空間一次性清除。這樣實現(xiàn)了對整個半?yún)^(qū)的GC,內(nèi)存分配時完全不用考慮碎片的情況。缺點在于這種算法將內(nèi)存的可用大小縮小了一半。

2.3 標記-整理算法

復制算法當對象存活率較高的情況時,照樣會出現(xiàn)效率低下的問題,另外內(nèi)存要浪費50%。為了避免上述問題,出現(xiàn)了 標記-整理算法。(mark-compact) 其標記過程與標記-清除算法一樣,但后續(xù)步驟不直接清除,而是讓所有存活的對象都向一端移動,然后直接清理掉邊界以外的內(nèi)存。

2.4分代收集法

根據(jù)對象的存活周期將內(nèi)存分為幾塊,如當前hotsport就分為新生代和老年代,然后在各個年代采用不同的收集算法。新生代采用復制算法,老年代采用標記清除或者標記整理算法。

3.垃圾收集器

垃圾收集器是內(nèi)存回收算法的具體實現(xiàn)。不同的廠商不同版本的虛擬機對垃圾收集器的實現(xiàn)有很大差別。在HotSport虛擬機1.7版本中,所有垃圾收集器如下圖所示:

HotSpot虛擬機的垃圾收集器

3.1 Serial收集器

Serial收集器是一個單線程收集器,只會使用一條線程去收集,同時需要暫停其他所有工作線程,直至收集結束。

Serial/Serial Old 收集器運行示意圖

優(yōu)點:
簡單高效,在單CPU環(huán)境中沒有線程開銷,可以獲得最大的效率。
適用于運行在Client模式下的虛擬機。

3.2 ParNew收集器

ParNew收集器是Serial收集器的多線程版本,除了多線程收集之外,其余包括控制參數(shù)、收集算法、對象分配規(guī)則、回收策略等都與Serial收集器一樣。

ParNew/Serial Old收集器運行示意圖

ParNew收集器是jvmServer模式下的首選新生代收集器,除Serial收集器外,只有ParNew收集器能與CMS收集器配合工作。默認開啟的收集線程數(shù)與CPU的數(shù)量相同??梢酝ㄟ^ -XX:parallelGCThreads參數(shù)來限制垃圾收集的線程數(shù)。

3.3 Parallel Scavenge收集器

Parallel Scavenge收集器是一個新生代收集器,也采用復制算法,并行多線程收集。特點在于達到一個可控目標吞吐量(Throughput)。
吞吐量 = 運行用戶代碼的時間/(運行用戶代碼的時間+GC耗時)。
-XX:MaxGCPauseMillis 設置停頓時間。
-XX:GCTimeratio 設置吞吐量。
Parallel Scavenge收集器 能夠根據(jù)上述兩個參數(shù)進行自適應調(diào)節(jié)。

3.4 Serial Old收集器

Serial Old收集器是Serial收集器的老年代版本,同樣式一個單線程收集器,使用標記整理算法。收集器的主要意義也是提供給Client模式下使用,在Server模式下,主要有兩大用途:

3.5 Parallel Old收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多線程的標記整理算法。
在注重吞吐量以及CPU資源敏感的場合,優(yōu)先考慮Parallel Scavenge和Parallel Old的組合進行收集。

parallel Scavenge/Parallel Old收集器運行示意圖

3.6 CMS 收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲得最短回收停頓時間為目標的收集器。主要應用在互聯(lián)網(wǎng)或BS系統(tǒng)的服務器上,這類應用尤其重視服務器的響應速度,希望停頓時間最短,以給用戶最好的體驗。
CMS時基于標記清除算法實現(xiàn)的,主要分為4個步驟:
初始標記(CMS initial mark):標記GC Roots能直接關聯(lián)到的對象,速度很快。
并發(fā)標記(CMS concurrent mark):進行roots tracing過程。
重新標記(CMS remark):修正并發(fā)標記階段因用戶程序繼續(xù)運作而導致標記產(chǎn)生變動的哪一部分對象的標記記錄,這個極端停頓時間比初始標記長。但遠比并發(fā)標記短。
并發(fā)清除(CMS concurrent sweep):回收資源。
上述步驟中,初始標記、重新標記這兩個步驟需要停止所有線程。

CMS收集器運行示意圖

CMS收集器缺點:
CMS收集器對CPU資源非常敏感,在CPU資源很匱乏時,效率會非常滴,造成停頓時間過長。
CMS收集器無法處理浮動垃圾,即在CMS收集器收集過程中新產(chǎn)生的垃圾,如果浮動垃圾較大,會導致CMS失敗。當CMS失敗后,會啟動后背預案,臨時啟用SerialOld收集器來進行老年代收集。這樣停頓時間就會比較長。
CMS收集器基于標記清除算法,會產(chǎn)生大量的內(nèi)存碎片,需要額外開啟內(nèi)存整理。通過參數(shù) -XX:CMSFullGCsBeforeCompation,設置執(zhí)行多少次不壓縮的GC后進行一次壓縮。

3.7 G1收集器

G1是一款面向服務端的垃圾收集器,具有如下特點:
并行與并發(fā):G1能充分利用多CPU,多環(huán)境下的硬件優(yōu)勢,使用多個CPU來縮短停頓時間,部分其他收集器需要停頓的動作,G1中可以并發(fā)的方式進行執(zhí)行。
分代收集:G1中仍然使用分代收集。
空間整合:G1基于標記整理算法實現(xiàn)收集,局部來看是基于復制算法,運行期間不會產(chǎn)生內(nèi)存碎片。
可預測停頓:可以指定停頓的時間片段。
G1可分為如下步驟:
初始標記(Initial marking)
并發(fā)標記(Concurrent marking)
最終標記(Final Marking)
篩選回收(Live Data Counting and evacuation)

G1收集器運行示意圖

4.垃圾回收器參數(shù)總結

參數(shù) 描述
UserSerialGC 虛擬機在client模式下的默認值,打開此開關后,用于Serial+Serial Old的收集器組合進行內(nèi)存回收
UserParNewGC 打開此開關 使用ParNew + Serial Old收集器組合進行內(nèi)存回收
UseConcMarkSweepGC 打開此開關,使用ParNew+CMS+Serial Old收集器組合進行內(nèi)存回收。Serial Old在CMS收集器出現(xiàn)concurrent Mode Failure 失敗后的后備收集器
UseParallelGC 在server模式下的默認值,打開此開關后使用Scavenge+Serial Old收集器組合進行回收
UseParallelOldGC 打開此開關后使用 Parallel Scavenge+Parallel Old收集器組合進行內(nèi)存回收
SurvivorRatio 新生代中Eden區(qū)域與Survivor區(qū)域的比值,默認為8,表示Eden:Survivor=8:1
PretenureSizeThreshold 直接晉升到老年代對象的大小,設置這個參數(shù)后大于這個參數(shù)的對象直接在老年代中分配
MaxTenuringThreshold 晉升老年代對象的年齡,每個對象堅持一次MnorGC年齡就加一,當超過這個參數(shù)值就進入老年代
UseAdaptiveSizePolicy 動態(tài)調(diào)整java堆各個區(qū)域的大小以及進入老年代的年齡
HandlePromotionFailure 是否允許分配擔保失敗,即老年代剩余空間不足以應付新生代整個對象都存活的特殊情況
ParalleGCThreads 設置并行GC時進行內(nèi)存回收的線程數(shù)
GCTimeratio GC時間占總時間比率,默認值為99,允許1%的GC時間。只在Parallel Seavenge收集器時生效
MaxGCPauseMillis 設置GC的最大停頓時間,只在Parallel Seavenge收集器時生效
CMSInitiatingOccupancyFration 設置CMS老年代空間被使用多少后觸發(fā)GC,默認值為68%,只在CMS收集器時生效
UseCMSCompactAtFullCollection 設置CMS收集器完成垃圾收集后是否需要進行一次碎片整理,只在CMS垃圾收集器時生效
CMSFullGCBeforeCompaction 設置CMS收集器進行若干次垃圾收集后再啟動一次內(nèi)存碎片整理,只在CMS垃圾收集器時生效

5. 內(nèi)存分配與回收策略

MinorGC:新生代發(fā)生的垃圾回收動作,一般速度比較快。
MajorGC/FullGC:發(fā)生在老年代的GC,出現(xiàn)MajorGC,經(jīng)常會伴隨一次MinorGC。MajorGC速度一般比MinorGC慢10倍以上。
1.大多數(shù)情況下,對象在Eden區(qū)中進行分配,當Eden中沒有足夠的分配空間時,虛擬機將進行一次MinorGC。
2.大對象直接進入老年代,避免觸發(fā)大量內(nèi)存復制。
3.長期存活的對象進入老年代。
4.動態(tài)對象年齡判定,為了能更好的適應不同的程序的內(nèi)存狀況,虛擬機并不是永遠需要要求對象的年齡達到MaxTenuringThreshold才能晉升老年代。如果在Survivor空間中相同年齡對象的大小總和大于Surrvivor空間的一半,則年齡大于等于該年齡的對象就可以直接進入老年代。
5.空間分配擔保:在發(fā)送MinorGC之前虛擬機會先檢查老年代最大可用的連續(xù)內(nèi)存空間是否大于新生代所有對象的總和,如果條件成了,則MinorGC可以確保安全。如果不成立,則會檢查是否設置了允許擔保失敗,如果允許,則會繼續(xù)檢查老年代最大可用連續(xù)內(nèi)存空間是否大于歷次晉升到老年代對象的平均大小,如果大于,將嘗試進行一次MinorGC,如果小于,則要進行一次FullGC。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容