當前位置:
首頁 > 知識 > JVM垃圾回收演算法及回收器詳解

JVM垃圾回收演算法及回收器詳解

本文主要講述JVM下幾種常見的垃圾回收演算法和相關的垃圾回收器,以及常見的和GC相關的性能調優參數。


垃圾回收演算法

標記清除

標記-清除演算法將垃圾回收分為兩個階段:標記階段和清除階段。在標記階段首先通過根節點,標記所有從根節點開始的對象,未被標記的對象就是未被引用的垃圾對象。然後,在清除階段,清除所有未被標記的對象。標記清除演算法帶來的一個問題是會存在大量的空間碎片,因為回收後的空間是不連續的,這樣給大對象分配內存的時候可能會提前觸發full gc。

JVM垃圾回收演算法及回收器詳解

複製演算法

將現有的內存空間分為兩快,每次只使用其中一塊,在垃圾回收時將正在使用的內存中的存活對象複製到未被使用的內存塊中,之後,清除正在使用的內存塊中的所有對象,交換兩個內存的角色,完成垃圾回收。

現在的商業虛擬機都採用這種收集演算法來回收新生代,IBM研究表明新生代中的對象98%是朝夕生死的,所以並不需要按照1:1的比例劃分內存空間,而是將內存分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中的一塊Survivor。當回收時,將Eden和Survivor中還存活著的對象一次性地拷貝到另外一個Survivor空間上,最後清理掉Eden和剛才用過的Survivor的空間。HotSpot虛擬機默認Eden和Survivor的大小比例是8:1(可以通過-SurvivorRattio來配置),也就是每次新生代中可用內存空間為整個新生代容量的90%,只有10%的內存會被「浪費」。當然,98%的對象可回收只是一般場景下的數據,我們沒有辦法保證回收都只有不多於10%的對象存活,當Survivor空間不夠用時,需要依賴其他內存(這裡指老年代)進行分配擔保。JVM垃圾回收演算法及回收器詳解

標記整理

複製演算法的高效性是建立在存活對象少、垃圾對象多的前提下的。這種情況在新生代經常發生,但是在老年代更常見的情況是大部分對象都是存活對象。如果依然使用複製演算法,由於存活的對象較多,複製的成本也將很高。

標記-壓縮演算法是一種老年代的回收演算法,它在標記-清除演算法的基礎上做了一些優化。首先也需要從根節點開始對所有可達對象做一次標記,但之後,它並不簡單地清理未標記的對象,而是將所有的存活對象壓縮到內存的一端。之後,清理邊界外所有的空間。這種方法既避免了碎片的產生,又不需要兩塊相同的內存空間,因此,其性價比比較高。JVM垃圾回收演算法及回收器詳解

增量演算法

增量演算法的基本思想是,如果一次性將所有的垃圾進行處理,需要造成系統長時間的停頓,那麼就可以讓垃圾收集線程和應用程序線程交替執行。每次,垃圾收集線程只收集一小片區域的內存空間,接著切換到應用程序線程。依次反覆,直到垃圾收集完成。使用這種方式,由於在垃圾回收過程中,間斷性地還執行了應用程序代碼,所以能減少系統的停頓時間。但是,因為線程切換和上下文轉換的消耗,會使得垃圾回收的總體成本上升,造成系統吞吐量的下降。


垃圾回收器

Serial收集器

Serial收集器是最古老的收集器,它的缺點是當Serial收集器想進行垃圾回收的時候,必須暫停用戶的所有進程,即stop the world。到現在為止,它依然是虛擬機運行在client模式下的默認新生代收集器,與其他收集器相比,對於限定在單個CPU的運行環境來說,Serial收集器由於沒有線程交互的開銷,專心做垃圾回收自然可以獲得最高的單線程收集效率。

Serial Old是Serial收集器的老年代版本,它同樣是一個單線程收集器,使用」標記-整理「演算法。這個收集器的主要意義也是被Client模式下的虛擬機使用。在Server模式下,它主要還有兩大用途:一個是在JDK1.5及以前的版本中與Parallel Scanvenge收集器搭配使用,另外一個就是作為CMS收集器的後備預案,在並發收集發生Concurrent Mode Failure的時候使用。

通過指定-UseSerialGC參數,使用Serial + Serial Old的串列收集器組合進行內存回收。

ParNew收集器

ParNew收集器是Serial收集器新生代的多線程實現,注意在進行垃圾回收的時候依然會stop the world,只是相比較Serial收集器而言它會運行多條進程進行垃圾回收。

ParNew收集器在單CPU的環境中絕對不會有比Serial收集器更好的效果,甚至由於存在線程交互的開銷,該收集器在通過超線程技術實現的兩個CPU的環境中都不能百分之百的保證能超越Serial收集器。當然,隨著可以使用的CPU的數量增加,它對於GC時系統資源的利用還是很有好處的。它默認開啟的收集線程數與CPU的數量相同,在CPU非常多(譬如32個,現在CPU動輒4核加超線程,伺服器超過32個邏輯CPU的情況越來越多了)的環境下,可以使用-XX:ParallelGCThreads參數來限制垃圾收集的線程數。

-UseParNewGC: 打開此開關後,使用ParNew + Serial Old的收集器組合進行內存回收,這樣新生代使用並行收集器,老年代使用串列收集器。

Parallel Scavenge收集器

Parallel是採用複製演算法的多線程新生代垃圾回收器,似乎和ParNew收集器有很多的相似的地方。但是Parallel Scanvenge收集器的一個特點是它所關注的目標是吞吐量(Throughput)。所謂吞吐量就是CPU用於運行用戶代碼的時間與CPU總消耗時間的比值,即吞吐量=運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間)。停頓時間越短就越適合需要與用戶交互的程序,良好的響應速度能夠提升用戶的體驗;而高吞吐量則可以最高效率地利用CPU時間,儘快地完成程序的運算任務,主要適合在後台運算而不需要太多交互的任務

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,採用多線程和」標記-整理」演算法。這個收集器是在jdk1.6中才開始提供的,在此之前,新生代的Parallel Scavenge收集器一直處於比較尷尬的狀態。原因是如果新生代Parallel Scavenge收集器,那麼老年代除了Serial Old(PS MarkSweep)收集器外別無選擇。由於單線程的老年代Serial Old收集器在服務端應用性能上的」拖累「,即使使用了Parallel Scavenge收集器也未必能在整體應用上獲得吞吐量最大化的效果,又因為老年代收集中無法充分利用伺服器多CPU的處理能力,在老年代很大而且硬體比較高級的環境中,這種組合的吞吐量甚至還不一定有ParNew加CMS的組合」給力「。直到Parallel Old收集器出現後,」吞吐量優先「收集器終於有了比較名副其實的應用祝賀,在注重吞吐量及CPU資源敏感的場合,都可以優先考慮Parallel Scavenge加Parallel Old收集器。

-UseParallelGC: 虛擬機運行在Server模式下的默認值,打開此開關後,使用Parallel Scavenge + Serial Old的收集器組合進行內存回收。

-UseParallelOldGC: 打開此開關後,使用Parallel Scavenge + Parallel Old的收集器組合進行垃圾回收

CMS收集器

CMS(Concurrent Mark Swep)收集器是一個比較重要的回收器,現在應用非常廣泛,我們重點來看一下,CMS一種獲取最短回收停頓時間為目標的收集器,這使得它很適合用於和用戶交互的業務。從名字(Mark Swep)就可以看出,CMS收集器是基於標記清除演算法實現的。它的收集過程分為四個步驟:

  1. 初始標記(initial mark)

  2. 並發標記(concurrent mark)

  3. 重新標記(remark)

  4. 並發清除(concurrent sweep)

注意初始標記和重新標記還是會stop the world,但是在耗費時間更長的並發標記和並發清除兩個階段都可以和用戶進程同時工作

不過由於CMS收集器是基於標記清除演算法實現的,會導致有大量的空間碎片產生,在為大對象分配內存的時候,往往會出現老年代還有很大的空間剩餘,但是無法找到足夠大的連續空間來分配當前對象,不得不提前開啟一次Full GC。為了解決這個問題,CMS收集器默認提供了一個-XX:+UseCMSCompactAtFullCollection收集開關參數(默認就是開啟的),用於在CMS收集器進行FullGC完開啟內存碎片的合併整理過程,內存整理的過程是無法並發的,這樣內存碎片問題倒是沒有了,不過停頓時間不得不變長。虛擬機設計者還提供了另外一個參數-XX:CMSFullGCsBeforeCompaction參數用於設置執行多少次不壓縮的FULL GC後跟著來一次帶壓縮的(默認值為0,表示每次進入Full GC時都進行碎片整理)。

不幸的是,它作為老年代的收集器,卻無法與jdk1.4中已經存在的新生代收集器Parallel Scavenge配合工作,所以在jdk1.5中使用cms來收集老年代的時候,新生代只能選擇ParNew或Serial收集器中的一個。ParNew收集器是使用-XX:+UseConcMarkSweepGC選項啟用CMS收集器之後的默認新生代收集器,也可以使用-XX:+UseParNewGC選項來強制指定它。

由於CMS收集器現在比較常用,下面我們再額外了解一下CMS演算法的幾個常用參數:

  • -XX:+CMSInitiatingOccupancyFraction:比如設置為75代表的就是第一個CMS垃圾回收會在老年代佔用75%的時候觸發,默認值是68。

  • -XX:UseCMSInitatingOccupancyOnly:表示只在到達閾值的時候,才進行 CMS 回收。

  • 為了減少第二次暫停的時間,通過-XX:+CMSParallelRemarkEnabled開啟並行remark。如果ramark時間還是過長的話,可以開啟-XX:+CMSScavengeBeforeRemark選項,強制remark之前開啟一次minor gc,減少remark的暫停時間,但是在remark之後也立即開始一次minor gc。

  • CMS默認啟動的回收線程數目是(ParallelGCThreads + 3)/4,如果你需要明確設定,可以通過-XX:+ParallelCMSThreads來設定,其中-XX:+ParallelGCThreads代表的年輕代的並發收集線程數目。

  • -XX:+CMSClassUnloadingEnabled: 允許對類元數據進行回收。

  • -XX:CMSInitatingPermOccupancyFraction:當永久區佔用率達到這一百分比後,啟動 CMS 回收 (前提是-XX:+CMSClassUnloadingEnabled 激活了)。

  • -XX:+CMSIncrementalMode:使用增量模式,比較適合單 CPU。

  • -XX:+UseCMSCompactAtFullCollection參數可以使 CMS 在垃圾收集完成後,進行一次內存碎片整理。內存碎片的整理並不是並發進行的。

  • -XX:+UseFullGCsBeforeCompaction:設定進行多少次 CMS 垃圾回收後,進行一次內存壓縮。

G1收集器

G1收集器是一款面向服務端應用的垃圾收集器。HotSpot團隊賦予它的使命是在未來替換掉JDK1.5中發布的CMS收集器。與其他GC收集器相比,G1具備如下特點:

  1. 並行與並發:G1能更充分的利用CPU,多核環境下的硬體優勢來縮短stop the world的停頓時間。

  2. 分代收集:和其他收集器一樣,分代的概念在G1中依然存在,不過G1不需要其他的垃圾回收器的配合就可以獨自管理整個GC堆。

  3. 空間整合:G1收集器有利於程序長時間運行,分配大對象時不會無法得到連續的空間而提前觸發一次GC。

  4. 可預測的非停頓:這是G1相對於CMS的另一大優勢,降低停頓時間是G1和CMS共同的關注點,能讓使用者明確指定在一個長度為M毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒。

在使用G1收集器時,Java堆的內存布局和其他收集器有很大的差別,它將這個Java堆分為多個大小相等的獨立區域,雖然還保留新生代和老年代的概念,但是新生代和老年代不再是物理隔離的了,它們都是一部分Region(不需要連續)的集合。

雖然G1看起來有很多優點,實際上CMS還是主流。


與GC相關的常用參數

除了上面提及的一些參數,下面補充一些和GC相關的常用參數:

  • -Xmx: 設置堆內存的最大值。

  • -XX:GCTimeRatio:設置吞吐量大小,它的值是一個 0-100 之間的整數。假設 GCTimeRatio 的值為 n,那麼系統將花費不超過 1/(1+n) 的時間用於垃圾收集。

  • -XX:MaxGCPauseMills:設置最大垃圾收集停頓時間。它的值是一個大於 0 的整數。收集器在工作時,會調整 Java 堆大小或者其他一些參數,儘可能地把停頓時間控制在 MaxGCPauseMills 以內。

  • -XX:ParallelGCThreads:設置用於垃圾回收的線程數。通常情況下可以和 CPU 數量相等。但在 CPU 數量比較多的情況下,設置相對較小的數值也是合理的。

  • -SurvivorRattio: 新生代Eden區域與Survivor區域的容量比值,默認為8,代表Eden: Suvivor= 8: 1。

  • -UseAdaptiveSizePolicy: 在這種模式下,新生代的大小、eden 和 survivor 的比例、晉陞老年代的對象年齡等參數會被自動調整,以達到在堆大小、吞吐量和停頓時間之間的平衡點。在手工調優比較困難的場合,可以直接使用這種自適應的方式,僅指定虛擬機的最大堆、目標的吞吐量 (GCTimeRatio) 和停頓時間 (MaxGCPauseMills),讓虛擬機自己完成調優工作。

  • -MaxTenuringThrehold: 晉陞到老年代的對象年齡。每個對象在堅持過一次Minor GC之後,年齡就會加1,當超過這個參數值時就進入老年代。

  • -PretenureSizeThreshold: 直接晉陞到老年代的對象大小,設置這個參數後,大於這個參數的對象將直接在老年代分配。

  • -Xss: 設置棧的大小。

  • -Xmn: 設置新生代的大小。

  • -Xms: 設置堆內存的初始值。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 IT優就業 的精彩文章:

Nancy基於JwtBearer認證的使用與實現
SLAM中的優化理論(一)——線性最小二乘
乾貨:基於 Git Flow 的 Git 最佳實踐
基於.NET CORE微服務框架-談談surging的服務容錯降級
for循環問題

TAG:IT優就業 |

您可能感興趣

垃圾回收 的基本演算法
廚餘垃圾回收
Go 語言的垃圾回收演化歷程:垃圾回收和運行時問題
1秒回收、5秒到賬,一桶收以智能垃圾回收機切入再生資源回收市場
家裡垃圾如何回收利用
蘋果上線回收服務:問題iPad Mini 2回收價感人
DNF 回收金幣活動 你被回收了多少
NASA「獵戶座」飛船回收團隊:回收工作一切準備就緒!
死亡遊戲,垃圾的可回收與不可回收……
黃金回收奢侈品回收 時一定要了解的
泰國停止進口可回收垃圾
可回收物品真的被回收了嗎?
Win7系統清空回收站不能刪除所有文件的解決方法
從iPhone X發布看閃回收蘋果手機回收時機選擇
手機回收ATM面世,無人回收會成為下一個風口嗎?
日產Leaf電池回收再利用模式
變廢為寶——垃圾的回收利用
垃圾廢品定期清理回收
微軟對電子垃圾回收商的入獄判決做出回應:「他在偽造Windows軟體。」
垃圾回收,我們該向台灣學什麼?