1.垃圾如何確認
對于大多數語言中判斷對象是否存活會采用引用計數法:給對象添加一個引用計數器,當有一個地方引用時,計數器就加1,當引用失效時,計數器就減1。任何時刻只要計數器為0則回收。但是這種算法無法解決對象之間互相循環引用的問題。如A引用B,而B又引用A,計數器永遠不為0,這兩個對象再也無任何引用。這樣GC不能回收這兩個對象。
因此,在JAVA中,采用了可達性分析算法來解決這個問題,判斷對象是否存活。
可達性分析算法:通過GCRoots的對象作為起點,從這些節點向下搜索,搜索走過的路徑稱之為引用鏈(Reference Chain),當一個對象到達GCRoots沒有任何鏈相連,則證明此對象不可用,可以被GC回收。
上圖藍色部分將會被GC回收。
2.垃圾收集算法
2.1 標記-清除算法
標記-清除算法是最基礎的垃圾收集算法。分為標記和清除兩個階段:
首先標記出需要回收的對象,在標記完成后統一回收所有被標記的對象。
存在的問題: 一是效率低,標記和清除兩個過程效率都不高。二是空間問題,標記清除后會產生大量的不連續的內存碎片。空間碎片太多會導致程序在運行過程中需要分配較大對象時無法找到連續內存而不得不提前觸發GC。
2.2復制算法
為了解決效率問題,復制算法應運而生。它將可用內存分為大小相等的兩塊,每次只使用其中一塊,當其中一塊內存耗盡,觸發GC時就將還存在的對象復制到另外一塊內存上面,然后再把已使用過的內存空間一次性清除。這樣實現了對整個半區的GC,內存分配時完全不用考慮碎片的情況。缺點在于這種算法將內存的可用大小縮小了一半。
2.3 標記-整理算法
復制算法當對象存活率較高的情況時,照樣會出現效率低下的問題,另外內存要浪費50%。為了避免上述問題,出現了 標記-整理算法。(mark-compact) 其標記過程與標記-清除算法一樣,但后續步驟不直接清除,而是讓所有存活的對象都向一端移動,然后直接清理掉邊界以外的內存。
2.4分代收集法
根據對象的存活周期將內存分為幾塊,如當前hotsport就分為新生代和老年代,然后在各個年代采用不同的收集算法。新生代采用復制算法,老年代采用標記清除或者標記整理算法。
3.垃圾收集器
垃圾收集器是內存回收算法的具體實現。不同的廠商不同版本的虛擬機對垃圾收集器的實現有很大差別。在HotSport虛擬機1.7版本中,所有垃圾收集器如下圖所示:
3.1 Serial收集器
Serial收集器是一個單線程收集器,只會使用一條線程去收集,同時需要暫停其他所有工作線程,直至收集結束。
優點:
簡單高效,在單CPU環境中沒有線程開銷,可以獲得最大的效率。
適用于運行在Client模式下的虛擬機。
3.2 ParNew收集器
ParNew收集器是Serial收集器的多線程版本,除了多線程收集之外,其余包括控制參數、收集算法、對象分配規則、回收策略等都與Serial收集器一樣。
ParNew收集器是jvmServer模式下的首選新生代收集器,除Serial收集器外,只有ParNew收集器能與CMS收集器配合工作。默認開啟的收集線程數與CPU的數量相同。可以通過 -XX:parallelGCThreads參數來限制垃圾收集的線程數。
3.3 Parallel Scavenge收集器
Parallel Scavenge收集器是一個新生代收集器,也采用復制算法,并行多線程收集。特點在于達到一個可控目標吞吐量(Throughput)。
吞吐量 = 運行用戶代碼的時間/(運行用戶代碼的時間+GC耗時)。
-XX:MaxGCPauseMillis 設置停頓時間。
-XX:GCTimeratio 設置吞吐量。
Parallel Scavenge收集器 能夠根據上述兩個參數進行自適應調節。
3.4 Serial Old收集器
Serial Old收集器是Serial收集器的老年代版本,同樣式一個單線程收集器,使用標記整理算法。收集器的主要意義也是提供給Client模式下使用,在Server模式下,主要有兩大用途:
3.5 Parallel Old收集器
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多線程的標記整理算法。
在注重吞吐量以及CPU資源敏感的場合,優先考慮Parallel Scavenge和Parallel Old的組合進行收集。
3.6 CMS 收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲得最短回收停頓時間為目標的收集器。主要應用在互聯網或BS系統的服務器上,這類應用尤其重視服務器的響應速度,希望停頓時間最短,以給用戶最好的體驗。
CMS時基于標記清除算法實現的,主要分為4個步驟:
初始標記(CMS initial mark):標記GC Roots能直接關聯到的對象,速度很快。
并發標記(CMS concurrent mark):進行roots tracing過程。
重新標記(CMS remark):修正并發標記階段因用戶程序繼續運作而導致標記產生變動的哪一部分對象的標記記錄,這個極端停頓時間比初始標記長。但遠比并發標記短。
并發清除(CMS concurrent sweep):回收資源。
上述步驟中,初始標記、重新標記這兩個步驟需要停止所有線程。
CMS收集器缺點:
CMS收集器對CPU資源非常敏感,在CPU資源很匱乏時,效率會非常滴,造成停頓時間過長。
CMS收集器無法處理浮動垃圾,即在CMS收集器收集過程中新產生的垃圾,如果浮動垃圾較大,會導致CMS失敗。當CMS失敗后,會啟動后背預案,臨時啟用SerialOld收集器來進行老年代收集。這樣停頓時間就會比較長。
CMS收集器基于標記清除算法,會產生大量的內存碎片,需要額外開啟內存整理。通過參數 -XX:CMSFullGCsBeforeCompation,設置執行多少次不壓縮的GC后進行一次壓縮。
3.7 G1收集器
G1是一款面向服務端的垃圾收集器,具有如下特點:
并行與并發:G1能充分利用多CPU,多環境下的硬件優勢,使用多個CPU來縮短停頓時間,部分其他收集器需要停頓的動作,G1中可以并發的方式進行執行。
分代收集:G1中仍然使用分代收集。
空間整合:G1基于標記整理算法實現收集,局部來看是基于復制算法,運行期間不會產生內存碎片。
可預測停頓:可以指定停頓的時間片段。
G1可分為如下步驟:
初始標記(Initial marking)
并發標記(Concurrent marking)
最終標記(Final Marking)
篩選回收(Live Data Counting and evacuation)
4.垃圾回收器參數總結
參數 | 描述 |
---|---|
UserSerialGC | 虛擬機在client模式下的默認值,打開此開關后,用于Serial+Serial Old的收集器組合進行內存回收 |
UserParNewGC | 打開此開關 使用ParNew + Serial Old收集器組合進行內存回收 |
UseConcMarkSweepGC | 打開此開關,使用ParNew+CMS+Serial Old收集器組合進行內存回收。Serial Old在CMS收集器出現concurrent Mode Failure 失敗后的后備收集器 |
UseParallelGC | 在server模式下的默認值,打開此開關后使用Scavenge+Serial Old收集器組合進行回收 |
UseParallelOldGC | 打開此開關后使用 Parallel Scavenge+Parallel Old收集器組合進行內存回收 |
SurvivorRatio | 新生代中Eden區域與Survivor區域的比值,默認為8,表示Eden:Survivor=8:1 |
PretenureSizeThreshold | 直接晉升到老年代對象的大小,設置這個參數后大于這個參數的對象直接在老年代中分配 |
MaxTenuringThreshold | 晉升老年代對象的年齡,每個對象堅持一次MnorGC年齡就加一,當超過這個參數值就進入老年代 |
UseAdaptiveSizePolicy | 動態調整java堆各個區域的大小以及進入老年代的年齡 |
HandlePromotionFailure | 是否允許分配擔保失敗,即老年代剩余空間不足以應付新生代整個對象都存活的特殊情況 |
ParalleGCThreads | 設置并行GC時進行內存回收的線程數 |
GCTimeratio | GC時間占總時間比率,默認值為99,允許1%的GC時間。只在Parallel Seavenge收集器時生效 |
MaxGCPauseMillis | 設置GC的最大停頓時間,只在Parallel Seavenge收集器時生效 |
CMSInitiatingOccupancyFration | 設置CMS老年代空間被使用多少后觸發GC,默認值為68%,只在CMS收集器時生效 |
UseCMSCompactAtFullCollection | 設置CMS收集器完成垃圾收集后是否需要進行一次碎片整理,只在CMS垃圾收集器時生效 |
CMSFullGCBeforeCompaction | 設置CMS收集器進行若干次垃圾收集后再啟動一次內存碎片整理,只在CMS垃圾收集器時生效 |
5. 內存分配與回收策略
MinorGC:新生代發生的垃圾回收動作,一般速度比較快。
MajorGC/FullGC:發生在老年代的GC,出現MajorGC,經常會伴隨一次MinorGC。MajorGC速度一般比MinorGC慢10倍以上。
1.大多數情況下,對象在Eden區中進行分配,當Eden中沒有足夠的分配空間時,虛擬機將進行一次MinorGC。
2.大對象直接進入老年代,避免觸發大量內存復制。
3.長期存活的對象進入老年代。
4.動態對象年齡判定,為了能更好的適應不同的程序的內存狀況,虛擬機并不是永遠需要要求對象的年齡達到MaxTenuringThreshold才能晉升老年代。如果在Survivor空間中相同年齡對象的大小總和大于Surrvivor空間的一半,則年齡大于等于該年齡的對象就可以直接進入老年代。
5.空間分配擔保:在發送MinorGC之前虛擬機會先檢查老年代最大可用的連續內存空間是否大于新生代所有對象的總和,如果條件成了,則MinorGC可以確保安全。如果不成立,則會檢查是否設置了允許擔保失敗,如果允許,則會繼續檢查老年代最大可用連續內存空間是否大于歷次晉升到老年代對象的平均大小,如果大于,將嘗試進行一次MinorGC,如果小于,則要進行一次FullGC。