4款Java垃圾回收器——錯誤的選擇致使糟糕的性能算法
提及垃圾回收,許多人都瞭解它的概念,也在平常的編程中有所應用。儘管如此,仍有許多咱們不太瞭解的東西,而這正是痛苦的根源。關於JVM最大的誤解就是認爲它只有一個垃圾回收器,而事實上它有四個不一樣的回收器,每一個都各有其長短。JVM並不會自動地選擇某一個,這事還得落在你個人肩上,由於不一樣的回收器會帶來吞吐量及應用的暫停時間的顯著的差別。編程
這四種回收算法的共同之處在於它們都是分代的,也就是說它們將託管的堆分紅了好幾個區域,它假設堆中的許多對象的生命週期都很短,能夠很快被回收掉。介紹這塊內容的已經不少了,所以這裏我打算直接講一下這幾個不一樣的算法,以及它們的長處及短處。數組
1.串行回收器架構
串行回收器是最簡單的一個,你都不會考慮使用它,由於它主要是面向單線程環境的(好比說32位的或者Windows)以及比較小的堆。這個回收器工做的時候會將全部應用線程所有凍結,就這一點而言就使得它徹底不可能會被服務端應用所採用。併發
如何使用它:你能夠打開-XX:+UseSerialGC這個JVM參數來使用它。ide
2.並行/吞吐量回收器微服務
下一個是並行回收器( Parallel collector)。這是JVM的默認回收器。正如它的名字所說的那樣,它的最大的優勢就是它使用多個線程來掃描及壓縮堆。它的缺點就是無論執行的是minor GC仍是full GC它都會暫停應用線程。並行回收器最適合那些能夠允許暫停的應用,它試圖減小由回收器所引發的CPU開銷。組件化
3.CMS回收器性能
並行回收器以後就是CMS回收器了(concurrent-mark-sweep)。這個算法使用了多個線程(concurrent)來掃描堆並標記(mark)那些再也不使用的能夠回收(sweep)的對象。這個算法在兩種狀況下會進入一個」stop the world」的模式:當進行根對象的初始標記的時候 (老生代中線程入口點或靜態變量可達的那些對象)以及當這個算法在併發運行的時候應用程序改變了堆的狀態使得它不得不回去再次確認本身標記的對象都是正確的。優化
使用這個回收器最大的問題就是會碰到promotion failure,這是指在回收新生代及年老代時出現了競爭條件的狀況。若是回收器須要將年輕的對象提高到年老代中,而這個時候年老代沒有多餘的空間了,它就只能先進行一次STW(Stop The World)的full GC了——這種狀況正是CMS所但願避免的。爲了確保這種狀況不會發生,你要麼就是增長老生代的大小(或者增長整個堆的大小),要麼就是給回收器分配一些後臺線程以便與對象分配的速度進行賽跑。
這個算法的另外一個缺點就是和並行回收器相比,它使用的CPU資源會更多,它使用了多個線程來執行掃描和回收,這樣才能讓應用持續提供更高級別的吞吐量。對於大多數長期運行的程序而言,應用的暫停對它們是很不利的,這個時候能夠考慮使用CMS回收器。儘管如此,這個算法也不是默認開啓的。你得指定XX:+UseConcMarkSweepGC來啓用它。假設你的堆小於4G,而你又但願分配更多的CPU資源以免應用暫停,那麼這就是你要選擇的回收器。然而,若是堆大於4G的話,你可能更但願使用最後的這個——G1回收器。
4.G1回收器
G1( Garbage first)回收器在JDK 7update 4中首次引入,它的設計目標是能更好地支持大於4GB的堆。G1回收器將堆分爲多個區域,大小從1MB到32MB不等,並使用多個後臺線程來掃描它們。G1回收器會優先掃描那些包含垃圾最多的區域,這正是它的名字的由來(Garbage first)。這個回收器能夠經過-XX:UseG1GC標記來啓用。
這一策略減小了後臺線程還未掃描完無用對象前堆就已經用光的可能性,而那種狀況回收器就必須得暫停應用,這就會致使STW回收。G1的另外一個好處就是它老是會進行堆的壓縮,而CMS回收器只有在full GC的時候纔會幹這事。
過去幾年裏,大堆一直都是一個充滿爭議的領域,不少開發人員從單機器單JVM模型轉向了單機器多JVM的微服務,組件化的架構。這是許多因素所驅動的,包括隔離程序的組件,簡化部署,避免從新加載應用類到內存所產生的開銷(Java 8中這點已經獲得了改善)。
儘管如此,這麼作最主要仍是但願能避免大堆的GC中長時期的」stop the world」的暫停(在一次大的回收中須要花費數秒才能完成)。像Docker這樣的容器技術也加速了這一進程,它們使得你能夠很輕鬆地在同一臺物理機上部署多個應用。
Java 8及G1回收器
Java 8 update 20所引入的一個很棒的優化就是G1回收器中的字符串去重(String deduplication)。因爲字符串(包括它們內部的char[]數組)佔用了大多數的堆空間,這項新的優化旨在使得G1回收器能識別出堆中那些重複出現的字符串並將它們指向同一個內部的char[]數組,以免同一個字符串的多份拷貝,那樣堆的使用效率會變得很低。你可使用-XX:+UseStringDeduplication這個JVM參數來試一下這個特性。
Java 8及持久代
Java 8中最大的改變就是持久代的移除,它本來是用來給類元數據,駐留字符串,靜態變量來分配空間的。這在之前都是須要開發人員來針對那些會加載大量類的應用來專門進行堆比例的優化及調整。許多年來都是如此,這也正是許多OutOfMemory異常的根源,所以由JVM來接管它真是再好不過了。即使如此,它自己並不會減小開發人員將應用解耦到不一樣的JVM中的可能性。
每一個回收器都有許多不一樣的開關和選項來進行調優,這可能會增長吞吐量,也可能會減小,這取決於你的應用的具體的行爲了。