現(xiàn)在已經(jīng)是2014年了,,但是對(duì)大多數(shù)開發(fā)人員而言有兩件事情仍然是個(gè)謎——垃圾回收以及異性(碼農(nóng)又被嘲笑了),。由于我對(duì)后者也不是特別了解,我想我還是試著說說前者吧,,尤其是隨著Java 8的到來,,這個(gè)領(lǐng)域也發(fā)生了許多重大的變化及提升,其中最重要的莫過于持久代(PermGen)的刪除以及一些令人振奮的新的優(yōu)化(后面會(huì)陸續(xù)提及這些),。 說起垃圾回收,許多人都了解它的概念,也在日常的編程中有所應(yīng)用,。盡管如此,,仍有許多我們不太了解的東西,而這正是痛苦的根源,。關(guān)于JVM最大的誤解就是認(rèn)為它只有一個(gè)垃圾回收器,而事實(shí)上它有四個(gè)不同的回收器,,每個(gè)都各有其長短,。JVM并不會(huì)自動(dòng)地選擇某一個(gè),這事還得落在你我的肩上,,因?yàn)椴煌幕厥掌鲿?huì)帶來吞吐量及應(yīng)用的暫停時(shí)間的顯著的差異,。 這四種回收算法的共同之處在于它們都是分代的,也就是說它們將托管的堆分成了好幾個(gè)區(qū)域,,它假設(shè)堆中的許多對(duì)象的生命周期都很短,,可以很快被回收掉。介紹這塊內(nèi)容的已經(jīng)很多了,,因此這里我打算直接講一下這幾個(gè)不同的算法,,以及它們的長處及短處。 1.串行回收器串行回收器是最簡單的一個(gè),,你都不會(huì)考慮使用它,,因?yàn)樗饕敲嫦騿尉€程環(huán)境的(比如說32位的或者Windows)以及比較小的堆。這個(gè)回收器工作的時(shí)候會(huì)將所有應(yīng)用線程全部凍結(jié),,就這一點(diǎn)而言就使得它完全不可能會(huì)被服務(wù)端應(yīng)用所采用,。 如何使用它:你可以打開-XX:+UseSerialGC這個(gè)JVM參數(shù)來使用它。 2.并行/吞吐量回收器下一個(gè)是并行回收器( Parallel collector),。這是JVM的默認(rèn)回收器。正如它的名字所說的那樣,,它的最大的優(yōu)點(diǎn)就是它使用多個(gè)線程來掃描及壓縮堆,。它的缺點(diǎn)就是不管執(zhí)行的是minor GC還是full GC它都會(huì)暫停應(yīng)用線程。并行回收器最適合那些可以容許暫停的應(yīng)用,,它試圖減少由回收器所引起的CPU開銷,。 3.CMS回收器并行回收器之后就是CMS回收器了(concurrent-mark-sweep)。這個(gè)算法使用了多個(gè)線程(concurrent)來掃描堆并標(biāo)記(mark)那些不再使用的可以回收(sweep)的對(duì)象,。這個(gè)算法在兩種情況下會(huì)進(jìn)入一個(gè)”stop the world”的模式:當(dāng)進(jìn)行根對(duì)象的初始標(biāo)記的時(shí)候 (老生代中線程入口點(diǎn)或靜態(tài)變量可達(dá)的那些對(duì)象)以及當(dāng)這個(gè)算法在并發(fā)運(yùn)行的時(shí)候應(yīng)用程序改變了堆的狀態(tài)使得它不得不回去再次確認(rèn)自己標(biāo)記的對(duì)象都是正確的,。 使用這個(gè)回收器最大的問題就是會(huì)碰到promotion failure,這是指在回收新生代及年老代時(shí)出現(xiàn)了競爭條件的情況,。如果回收器需要將年輕的對(duì)象提升到年老代中,,而這個(gè)時(shí)候年老代沒有多余的空間了,它就只能先進(jìn)行一次STW(Stop The World)的full GC了——這種情況正是CMS所希望避免的。為了確保這種情況不會(huì)發(fā)生,,你要么就是增加老生代的大?。ɑ蛘咴黾诱麄€(gè)堆的大小),,要么就是給回收器分配一些后臺(tái)線程以便與對(duì)象分配的速度進(jìn)行賽跑,。 這個(gè)算法的另一個(gè)缺點(diǎn)就是和并行回收器相比,它使用的CPU資源會(huì)更多,,它使用了多個(gè)線程來執(zhí)行掃描和回收,,這樣才能讓應(yīng)用持續(xù)提供更高級(jí)別的吞吐量。對(duì)于大多數(shù)長期運(yùn)行的程序而言,,應(yīng)用的暫停對(duì)它們是很不利的,,這個(gè)時(shí)候可以考慮使用CMS回收器。盡管如此,,這個(gè)算法也不是默認(rèn)開啟的,。你得指定XX:+UseConcMarkSweepGC來啟用它。假設(shè)你的堆小于4G,,而你又希望分配更多的CPU資源以避免應(yīng)用暫停,,那么這就是你要選擇的回收器。然而,,如果堆大于4G的話,,你可能更希望使用最后的這個(gè)——G1回收器。 4.G1回收器G1( Garbage first)回收器在JDK 7update 4中首次引入,,它的設(shè)計(jì)目標(biāo)是能更好地支持大于4GB的堆,。G1回收器將堆分為多個(gè)區(qū)域,大小從1MB到32MB不等,,并使用多個(gè)后臺(tái)線程來掃描它們,。G1回收器會(huì)優(yōu)先掃描那些包含垃圾最多的區(qū)域,這正是它的名字的由來(Garbage first),。這個(gè)回收器可以通過-XX:UseG1GC標(biāo)記來啟用,。 這一策略減少了后臺(tái)線程還未掃描完無用對(duì)象前堆就已經(jīng)用光的可能性,而那種情況回收器就必須得暫停應(yīng)用,,這就會(huì)導(dǎo)致STW回收,。G1的另一個(gè)好處就是它總是會(huì)進(jìn)行堆的壓縮,而CMS回收器只有在full GC的時(shí)候才會(huì)干這事,。 過去幾年里,,大堆一直都是一個(gè)充滿爭議的領(lǐng)域,很多開發(fā)人員從單機(jī)器單JVM模型轉(zhuǎn)向了單機(jī)器多JVM的微服務(wù),,組件化的架構(gòu),。這是許多因素所驅(qū)動(dòng)的,包括隔離程序的組件,簡化部署,,避免重新加載應(yīng)用類到內(nèi)存所產(chǎn)生的開銷(Java 8中這點(diǎn)已經(jīng)得到了改善),。 盡管如此,這么做最主要還是希望能避免大堆的GC中長時(shí)期的”stop the world”的暫停(在一次大的回收中需要花費(fèi)數(shù)秒才能完成),。像Docker這樣的容器技術(shù)也加速了這一進(jìn)程,,它們使得你可以很輕松地在同一臺(tái)物理機(jī)上部署多個(gè)應(yīng)用。 Java 8及G1回收器Java 8 update 20所引入的一個(gè)很棒的優(yōu)化就是G1回收器中的字符串去重(String deduplication),。由于字符串(包括它們內(nèi)部的char[]數(shù)組)占用了大多數(shù)的堆空間,,這項(xiàng)新的優(yōu)化旨在使得G1回收器能識(shí)別出堆中那些重復(fù)出現(xiàn)的字符串并將它們指向同一個(gè)內(nèi)部的char[]數(shù)組,以避免同一個(gè)字符串的多份拷貝,,那樣堆的使用效率會(huì)變得很低,。你可以使用-XX:+UseStringDeduplication這個(gè)JVM參數(shù)來試一下這個(gè)特性。 Java 8及持久代Java 8中最大的改變就是持久代的移除,,它原本是用來給類元數(shù)據(jù),,駐留字符串,靜態(tài)變量來分配空間的,。這在以前都是需要開發(fā)人員來針對(duì)那些會(huì)加載大量類的應(yīng)用來專門進(jìn)行堆比例的優(yōu)化及調(diào)整,。許多年來都是如此,這也正是許多OutOfMemory異常的根源,,因此由JVM來接管它真是再好不過了,。即便如此,它本身并不會(huì)減少開發(fā)人員將應(yīng)用解耦到不同的JVM中的可能性,。 每個(gè)回收器都有許多不同的開關(guān)和選項(xiàng)來進(jìn)行調(diào)優(yōu),,這可能會(huì)增加吞吐量,也可能會(huì)減少,,這取決于你的應(yīng)用的具體的行為了,。在下一篇文章中我們會(huì)深入講解配置這些算法的關(guān)鍵策略。 隱者黑鷹 |
|