溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

詳解 Java性能優(yōu)化和JVM GC(垃圾回收機(jī)制)

發(fā)布時(shí)間:2020-08-10 19:06:12 來源:ITPUB博客 閱讀:203 作者:慕容扶蘇 欄目:編程語言

Java的性能優(yōu)化,JVM GC(垃圾回收機(jī)制)在學(xué)習(xí)Java GC 之前,我們需要記住一個(gè)單詞:stop-the-world 。它會(huì)在任何一種GC算法中發(fā)生。stop-the-world 意味著JVM因?yàn)樾枰獔?zhí)行GC而停止了應(yīng)用程序的執(zhí)行。當(dāng)stop-the-world 發(fā)生時(shí),除GC所需的線程外,所有的線程都進(jìn)入等待狀態(tài),直到GC任務(wù)完成。GC優(yōu)化很多時(shí)候就是減少stop-the-world 的發(fā)生。

JVM GC回收哪個(gè)區(qū)域內(nèi)的垃圾? 需要注意的是,JVM GC只回收堆區(qū)和方法區(qū)內(nèi)的對(duì)象。而棧區(qū)的數(shù)據(jù),在超出作用域后會(huì)被JVM自動(dòng)釋放掉,所以其不在JVM GC的管理范圍內(nèi)。

JVM GC怎么判斷對(duì)象可以被回收了? · 對(duì)象沒有引用 · 作用域發(fā)生未捕獲異常 · 程序在作用域正常執(zhí)行完畢 · 程序執(zhí)行了System.exit() · 程序發(fā)生意外終止(被殺線程等) 在Java程序中不能顯式的分配和注銷緩存,因?yàn)檫@些事情JVM都幫我們做了,那就是GC。 有些時(shí)候我們可以將相關(guān)的對(duì)象設(shè)置成null 來試圖顯示的清除緩存,但是并不是設(shè)置為null 就會(huì)一定被標(biāo)記為可回收,有可能會(huì)發(fā)生逃逸。 將對(duì)象設(shè)置成null 至少?zèng)]有什么壞處,但是使用System.gc() 便不可取了,使用System.gc() 時(shí)候并不是馬上執(zhí)行GC操作,而是會(huì)等待一段時(shí)間,甚至不執(zhí)行,而且System.gc() 如果被執(zhí)行,會(huì)觸發(fā)Full GC ,這非常影響性能。

JVM GC什么時(shí)候執(zhí)行? eden區(qū)空間不夠存放新對(duì)象的時(shí)候,執(zhí)行Minro GC。升到老年代的對(duì)象大于老年代剩余空間的時(shí)候執(zhí)行Full GC,或者小于的時(shí)候被HandlePromotionFailure 參數(shù)強(qiáng)制Full GC 。調(diào)優(yōu)主要是減少 Full GC 的觸發(fā)次數(shù),可以通過 NewRatio 控制新生代轉(zhuǎn)老年代的比例,通過MaxTenuringThreshold 設(shè)置對(duì)象進(jìn)入老年代的年齡閥值(后面會(huì)介紹到)。

按代的垃圾回收機(jī)制 新生代(Young generation):絕大多數(shù)最新被創(chuàng)建的對(duì)象都會(huì)被分配到這里,由于大部分在創(chuàng)建后很快變得不可達(dá),很多對(duì)象被創(chuàng)建在新生代,然后“消失”。對(duì)象從這個(gè)區(qū)域“消失”的過程我們稱之為:Minor GC 。 老年代(Old generation):對(duì)象沒有變得不可達(dá),并且從新生代周期中存活了下來,會(huì)被拷貝到這里。其區(qū)域分配的空間要比新生代多。也正由于其相對(duì)大的空間,發(fā)生在老年代的GC次數(shù)要比新生代少得多。對(duì)象從老年代中消失的過程,稱之為:Major GC 或者 Full GC。 持久代(Permanent generation)也稱之為 方法區(qū)(Method area):用于保存類常量以及字符串常量。注意,這個(gè)區(qū)域不是用于存儲(chǔ)那些從老年代存活下來的對(duì)象,這個(gè)區(qū)域也可能發(fā)生GC。發(fā)生在這個(gè)區(qū)域的GC事件也被算為 Major GC 。只不過在這個(gè)區(qū)域發(fā)生GC的條件非常嚴(yán)苛,必須符合以下三種條件才會(huì)被回收: 1、所有實(shí)例被回收 2、加載該類的ClassLoader 被回收 3、Class 對(duì)象無法通過任何途徑訪問(包括反射) 可能我們會(huì)有疑問: 如果老年代的對(duì)象需要引用新生代的對(duì)象,會(huì)發(fā)生什么呢? 為了解決這個(gè)問題,老年代中存在一個(gè) card table ,它是一個(gè)512byte大小的塊。所有老年代的對(duì)象指向新生代對(duì)象的引用都會(huì)被記錄在這個(gè)表中。當(dāng)針對(duì)新生代執(zhí)行GC的時(shí)候,只需要查詢 card table 來決定是否可以被回收,而不用查詢整個(gè)老年代。這個(gè) card table 由一個(gè)write barrier 來管理。write barrier給GC帶來了很大的性能提升,雖然由此可能帶來一些開銷,但完全是值得的。

默認(rèn)的新生代(Young generation)、老年代(Old generation)所占空間比例為 1 : 2 。

新生代空間的構(gòu)成與邏輯 為了更好的理解GC,我們來學(xué)習(xí)新生代的構(gòu)成,它用來保存那些第一次被創(chuàng)建的對(duì)象,它被分成三個(gè)空間: · 一個(gè)伊甸園空間(Eden) · 兩個(gè)幸存者空間(Fron Survivor、To Survivor) 默認(rèn)新生代空間的分配:Eden : Fron : To = 8 : 1 : 1

每個(gè)空間的執(zhí)行順序如下: 1、絕大多數(shù)剛剛被創(chuàng)建的對(duì)象會(huì)存放在伊甸園空間(Eden)。 2、在伊甸園空間執(zhí)行第一次GC(Minor GC)之后,存活的對(duì)象被移動(dòng)到其中一個(gè)幸存者空間(Survivor)。 3、此后,每次伊甸園空間執(zhí)行GC后,存活的對(duì)象會(huì)被堆積在同一個(gè)幸存者空間。 4、當(dāng)一個(gè)幸存者空間飽和,還在存活的對(duì)象會(huì)被移動(dòng)到另一個(gè)幸存者空間。然后會(huì)清空已經(jīng)飽和的哪個(gè)幸存者空間。 5、在以上步驟中重復(fù)N次(N = MaxTenuringThreshold(年齡閥值設(shè)定,默認(rèn)15))依然存活的對(duì)象,就會(huì)被移動(dòng)到老年代。 從上面的步驟可以發(fā)現(xiàn),兩個(gè)幸存者空間,必須有一個(gè)是保持空的。如果兩個(gè)兩個(gè)幸存者空間都有數(shù)據(jù),或兩個(gè)空間都是空的,那一定是你的系統(tǒng)出現(xiàn)了某種錯(cuò)誤。 我們需要重點(diǎn)記住的是,對(duì)象在剛剛被創(chuàng)建之后,是保存在伊甸園空間的(Eden)。那些長(zhǎng)期存活的對(duì)象會(huì)經(jīng)由幸存者空間(Survivor)轉(zhuǎn)存到老年代空間(Old generation)。 也有例外出現(xiàn),對(duì)于一些比較大的對(duì)象(需要分配一塊比較大的連續(xù)內(nèi)存空間)則直接進(jìn)入到老年代。一般在Survivor 空間不足的情況下發(fā)生。

老年代空間的構(gòu)成與邏輯 老年代空間的構(gòu)成其實(shí)很簡(jiǎn)單,它不像新生代空間那樣劃分為幾個(gè)區(qū)域,它只有一個(gè)區(qū)域,里面存儲(chǔ)的對(duì)象并不像新生代空間絕大部分都是朝聞道,夕死矣。這里的對(duì)象幾乎都是從Survivor 空間中熬過來的,它們絕不會(huì)輕易的狗帶。因此,F(xiàn)ull GC(Major GC)發(fā)生的次數(shù)不會(huì)有Minor GC 那么頻繁,并且做一次Major GC 的時(shí)間比Minor GC 要更長(zhǎng)(約10倍)。

JVM GC 算法講解 1、根搜索算法 根搜索算法是從離散數(shù)學(xué)中的圖論引入的,程序把所有引用關(guān)系看作一張圖,從一個(gè)節(jié)點(diǎn)GC ROOT 開始,尋找對(duì)應(yīng)的引用節(jié)點(diǎn),找到這個(gè)節(jié)點(diǎn)后,繼續(xù)尋找這個(gè)節(jié)點(diǎn)的引用節(jié)點(diǎn)。當(dāng)所有的引用節(jié)點(diǎn)尋找完畢后,剩余的節(jié)點(diǎn)則被認(rèn)為是沒有被引用到的節(jié)點(diǎn),即無用的節(jié)點(diǎn)。

上圖紅色為無用的節(jié)點(diǎn),可以被回收。 目前Java中可以作為GC ROOT的對(duì)象有: 

1、虛擬機(jī)棧中引用的對(duì)象(本地變量表)

 2、方法區(qū)中靜態(tài)屬性引用的對(duì)象 

3、方法區(qū)中常量引用的對(duì)象 

4、本地方法棧中引用的對(duì)象(Native對(duì)象) 基本所有GC算法都引用根搜索算法這種概念。

2、標(biāo)記 - 清除算法 標(biāo)記-清除算法采用從根集合進(jìn)行掃描,對(duì)存活的對(duì)象進(jìn)行標(biāo)記,標(biāo)記完畢后,再掃描整個(gè)空間中未被標(biāo)記的對(duì)象進(jìn)行直接回收,如上圖。 標(biāo)記-清除算法不需要進(jìn)行對(duì)象的移動(dòng),并且僅對(duì)不存活的對(duì)象進(jìn)行處理,在存活的對(duì)象比較多的情況下極為高效,但由于標(biāo)記-清除算法直接回收不存活的對(duì)象,并沒有對(duì)還存活的對(duì)象進(jìn)行整理,因此會(huì)導(dǎo)致內(nèi)存碎片。

3、復(fù)制算法

復(fù)制算法將內(nèi)存劃分為兩個(gè)區(qū)間,使用此算法時(shí),所有動(dòng)態(tài)分配的對(duì)象都只能分配在其中一個(gè)區(qū)間(活動(dòng)區(qū)間),而另外一個(gè)區(qū)間(空間區(qū)間)則是空閑的。

復(fù)制算法采用從根集合掃描,將存活的對(duì)象復(fù)制到空閑區(qū)間,當(dāng)掃描完畢活動(dòng)區(qū)間后,會(huì)的將活動(dòng)區(qū)間一次性全部回收。此時(shí)原本的空閑區(qū)間變成了活動(dòng)區(qū)間。下次GC時(shí)候又會(huì)重復(fù)剛才的操作,以此循環(huán)。 復(fù)制算法在存活對(duì)象比較少的時(shí)候,極為高效,但是帶來的成本是犧牲一半的內(nèi)存空間用于進(jìn)行對(duì)象的移動(dòng)。所以復(fù)制算法的使用場(chǎng)景,必須是對(duì)象的存活率非常低才行,而且最重要的是,我們需要克服50%內(nèi)存的浪費(fèi)。

4、標(biāo)記 - 整理算法

標(biāo)記-整理算法采用 標(biāo)記-清除 算法一樣的方式進(jìn)行對(duì)象的標(biāo)記、清除,但在回收不存活的對(duì)象占用的空間后,會(huì)將所有存活的對(duì)象往左端空閑空間移動(dòng),并更新對(duì)應(yīng)的指針。標(biāo)記-整理 算法是在標(biāo)記-清除 算法之上,又進(jìn)行了對(duì)象的移動(dòng)排序整理,因此成本更高,但卻解決了內(nèi)存碎片的問題。

JVM為了優(yōu)化內(nèi)存的回收,使用了分代回收的方式,對(duì)于新生代內(nèi)存的回收(Minor GC)主要采用復(fù)制算法。而對(duì)于老年代的回收(Major GC),大多采用標(biāo)記-整理算法。

垃圾回收器簡(jiǎn)介 需要注意的是,每一個(gè)回收器都存在Stop The World 的問題,只不過各個(gè)回收器在Stop The World 時(shí)間優(yōu)化程度、算法的不同,可根據(jù)自身需求選擇適合的回收器。 1、Serial(-XX:+UseSerialGC) 從名字我們可以看出,這是一個(gè)串行收集器。 Serial收集器是Java虛擬機(jī)中最基本、歷史最悠久的收集器。在JDK1.3之前是Java虛擬機(jī)新生代收集器的唯一選擇。目前也是ClientVM下ServerVM 4核4GB以下機(jī)器默認(rèn)垃圾回收器。Serial收集器并不是只能使用一個(gè)CPU進(jìn)行收集,而是當(dāng)JVM需要進(jìn)行垃圾回收的時(shí)候,需暫停所有的用戶線程,直到回收結(jié)束。

使用算法:復(fù)制算法

JVM中文名稱為Java虛擬機(jī),因此它像一臺(tái)虛擬的電腦在工作,而其中的每一個(gè)線程都被認(rèn)為是JVM的一個(gè)處理器,因此圖中的CPU0、CPU1實(shí)際上為用戶的線程,而不是真正的機(jī)器CPU,不要誤解哦。

Serial收集器雖然是最老的,但是它對(duì)于限定單個(gè)CPU的環(huán)境來說,由于沒有線程交互的開銷,專心做垃圾收集,所以它在這種情況下是相對(duì)于其他收集器中最高效的。

2、SerialOld(-XX:+UseSerialGC) SerialOld是Serial收集器的老年代收集器版本,它同樣是一個(gè)單線程收集器,這個(gè)收集器目前主要用于Client模式下使用。如果在Server模式下,它主要還有兩大用途:一個(gè)是在JDK1.5及之前的版本中與Parallel Scavenge收集器搭配使用,另外一個(gè)就是作為CMS收集器的后備預(yù)案,如果CMS出現(xiàn)Concurrent Mode Failure,則SerialOld將作為后備收集器。 使用算法:標(biāo)記 - 整理算法 運(yùn)行示意圖與上圖一致。

3、ParNew(-XX:+UseParNewGC) ParNew其實(shí)就是Serial收集器的多線程版本。除了Serial收集器外,只有它能與CMS收集器配合工作。 

ParNew是許多運(yùn)行在Server模式下的JVM首選的新生代收集器。但是在單CPU的情況下,它的效率遠(yuǎn)遠(yuǎn)低于Serial收集器,所以一定要注意使用場(chǎng)景。

4、ParallelScavenge(-XX:+UseParallelGC) ParallelScavenge又被稱為吞吐量?jī)?yōu)先收集器,和ParNew 收集器類似,是一個(gè)新生代收集器。 使用算法:復(fù)制算法 ParallelScavenge收集器的目標(biāo)是達(dá)到一個(gè)可控件的吞吐量,所謂吞吐量就是CPU用于運(yùn)行用戶代碼的時(shí)間與CPU總消耗時(shí)間的比值,即吞吐量 = 運(yùn)行用戶代碼時(shí)間 / (運(yùn)行用戶代碼時(shí)間 + 垃圾收集時(shí)間)。如果虛擬機(jī)總共運(yùn)行了100分鐘,其中垃圾收集花了1分鐘,那么吞吐量就是99% 。

5、ParallelOld(-XX:+UseParallelOldGC) ParallelOld是并行收集器,和SerialOld一樣,ParallelOld是一個(gè)老年代收集器,是老年代吞吐量?jī)?yōu)先的一個(gè)收集器。這個(gè)收集器在JDK1.6之后才開始提供的,在此之前,ParallelScavenge只能選擇SerialOld來作為其老年代的收集器,這嚴(yán)重拖累了ParallelScavenge整體的速度。而ParallelOld的出現(xiàn)后,“吞吐量?jī)?yōu)先”收集器才名副其實(shí)! 使用算法:標(biāo)記 - 整理算法

在注重吞吐量與CPU數(shù)量大于1的情況下,都可以優(yōu)先考慮ParallelScavenge + ParalleloOld收集器。

6、CMS (-XX:+UseConcMarkSweepGC) CMS是一個(gè)老年代收集器,全稱 Concurrent Low Pause Collector,是JDK1.4后期開始引用的新GC收集器,在JDK1.5、1.6中得到了進(jìn)一步的改進(jìn)。它是對(duì)于響應(yīng)時(shí)間的重要性需求大于吞吐量要求的收集器。對(duì)于要求服務(wù)器響應(yīng)速度高的情況下,使用CMS非常合適。 CMS的一大特點(diǎn),就是用兩次短暫的暫停來代替串行或并行標(biāo)記整理算法時(shí)候的長(zhǎng)暫停。 使用算法:標(biāo)記 - 清理 CMS的執(zhí)行過程如下: · 初始標(biāo)記(STW initial mark) 在這個(gè)階段,需要虛擬機(jī)停頓正在執(zhí)行的應(yīng)用線程,官方的叫法STW(Stop Tow World)。這個(gè)過程從根對(duì)象掃描直接關(guān)聯(lián)的對(duì)象,并作標(biāo)記。這個(gè)過程會(huì)很快的完成。 · 并發(fā)標(biāo)記(Concurrent marking) 這個(gè)階段緊隨初始標(biāo)記階段,在“初始標(biāo)記”的基礎(chǔ)上繼續(xù)向下追溯標(biāo)記。注意這里是并發(fā)標(biāo)記,表示用戶線程可以和GC線程一起并發(fā)執(zhí)行,這個(gè)階段不會(huì)暫停用戶的線程哦。 · 并發(fā)預(yù)清理(Concurrent precleaning) 這個(gè)階段任然是并發(fā)的,JVM查找正在執(zhí)行“并發(fā)標(biāo)記”階段時(shí)候進(jìn)入老年代的對(duì)象(可能這時(shí)會(huì)有對(duì)象從新生代晉升到老年代,或被分配到老年代)。通過重新掃描,減少在一個(gè)階段“重新標(biāo)記”的工作,因?yàn)橄乱浑A段會(huì)STW。 · 重新標(biāo)記(STW remark) 這個(gè)階段會(huì)再次暫停正在執(zhí)行的應(yīng)用線程,重新重根對(duì)象開始查找并標(biāo)記并發(fā)階段遺漏的對(duì)象(在并發(fā)標(biāo)記階段結(jié)束后對(duì)象狀態(tài)的更新導(dǎo)致),并處理對(duì)象關(guān)聯(lián)。這一次耗時(shí)會(huì)比“初始標(biāo)記”更長(zhǎng),并且這個(gè)階段可以并行標(biāo)記。 · 并發(fā)清理(Concurrent sweeping) 這個(gè)階段是并發(fā)的,應(yīng)用線程和GC清除線程可以一起并發(fā)執(zhí)行。 · 并發(fā)重置(Concurrent reset) 這個(gè)階段任然是并發(fā)的,重置CMS收集器的數(shù)據(jù)結(jié)構(gòu),等待下一次垃圾回收。

CMS的缺點(diǎn):

 1、內(nèi)存碎片。由于使用了 標(biāo)記-清理 算法,導(dǎo)致內(nèi)存空間中會(huì)產(chǎn)生內(nèi)存碎片。不過CMS收集器做了一些小的優(yōu)化,就是把未分配的空間匯總成一個(gè)列表,當(dāng)有JVM需要分配內(nèi)存空間的時(shí)候,會(huì)搜索這個(gè)列表找到符合條件的空間來存儲(chǔ)這個(gè)對(duì)象。但是內(nèi)存碎片的問題依然存在,如果一個(gè)對(duì)象需要3塊連續(xù)的空間來存儲(chǔ),因?yàn)閮?nèi)存碎片的原因,尋找不到這樣的空間,就會(huì)導(dǎo)致Full GC。 

2、需要更多的CPU資源。由于使用了并發(fā)處理,很多情況下都是GC線程和應(yīng)用線程并發(fā)執(zhí)行的,這樣就需要占用更多的CPU資源,也是犧牲了一定吞吐量的原因。 

3、需要更大的堆空間。因?yàn)镃MS標(biāo)記階段應(yīng)用程序的線程還是執(zhí)行的,那么就會(huì)有堆空間繼續(xù)分配的問題,為了保障CMS在回收堆空間之前還有空間分配給新加入的對(duì)象,必須預(yù)留一部分空間。CMS默認(rèn)在老年代空間使用68%時(shí)候啟動(dòng)垃圾回收。可以通過-XX:CMSinitiatingOccupancyFraction=n來設(shè)置這個(gè)閥值。

7、GarbageFirst(G1) 這是一個(gè)新的垃圾回收器,既可以回收新生代也可以回收老年代,SunHotSpot1.6u14以上EarlyAccess版本加入了這個(gè)回收器,Sun公司預(yù)期SunHotSpot1.7發(fā)布正式版本。通過重新劃分內(nèi)存區(qū)域,整合優(yōu)化CMS,同時(shí)注重吞吐量和響應(yīng)時(shí)間。杯具的是Oracle收購這個(gè)收集器之后將其用于商用收費(fèi)版收集器。因此目前暫時(shí)沒有發(fā)現(xiàn)哪個(gè)公司使用它,這個(gè)放在之后再去研究吧。

整理一下新生代和老年代的收集器。 新生代收集器: Serial (-XX:+UseSerialGC) ParNew(-XX:+UseParNewGC) ParallelScavenge(-XX:+UseParallelGC) G1 收集器

老年代收集器: SerialOld(-XX:+UseSerialOldGC) ParallelOld(-XX:+UseParallelOldGC) CMS(-XX:+UseConcMarkSweepGC) G1 收集器

8、調(diào)優(yōu)jvm參數(shù)介紹 堆設(shè)置 -Xmx3550m:設(shè)置JVM最大堆內(nèi)存 為3550M。 -Xms3550m:設(shè)置JVM初始堆內(nèi)存 為3550M。此值可以設(shè)置與-Xmx相同,以避免每次垃圾回收完成后JVM重新分配內(nèi)存。 -Xss128k:設(shè)置每個(gè)線程的棧 大小。JDK5.0以后每個(gè)線程棧大小為1M,之前每個(gè)線程棧大小為256K。應(yīng)當(dāng)根據(jù)應(yīng)用的線程所需內(nèi)存大小進(jìn)行調(diào)整。在相同物理內(nèi)存下,減小這個(gè)值能 生成更多的線程。但是操作系統(tǒng)對(duì)一個(gè)進(jìn)程內(nèi)的線程數(shù)還是有限制的,不能無限生成,經(jīng)驗(yàn)值在3000~5000左右。 -Xmn2g:設(shè)置堆內(nèi)存年輕代 大小為2G。整個(gè)堆內(nèi)存大小 = 年輕代大小 + 年老代大小 + 持久代大小 。持久代一般固定大小為64m,所以增大年輕代后,將會(huì)減小年老代大小。此值對(duì)系統(tǒng)性能影響較大,Sun官方推薦配置為整個(gè)堆的3/8。 -XX:PermSize=256M:設(shè)置堆內(nèi)存持久代 初始值為256M。(貌似是Eclipse等IDE的初始化參數(shù)) -XX:MaxNewSize=size:新生成的對(duì)象能占用內(nèi)存的最大值。 -XX:MaxPermSize=512M:設(shè)置持久代最大值為512M。 -XX:NewRatio=4:設(shè)置堆內(nèi)存年輕代(包括Eden和兩個(gè)Survivor區(qū))與堆內(nèi)存年老代的比值(除去持久代) 。設(shè)置為4,則年輕代所占與年老代所占的比值為1:4。 -XX:SurvivorRatio=4:設(shè)置堆內(nèi)存年輕代中Eden區(qū)與Survivor區(qū)大小的比值 。設(shè)置為4,則兩個(gè)Survivor區(qū)(JVM堆內(nèi)存年輕代中默認(rèn)有2個(gè)Survivor區(qū))與一個(gè)Eden區(qū)的比值為2:4,一個(gè)Survivor區(qū)占 整個(gè)年輕代的1/6。 -XX:MaxTenuringThreshold=7:表示一個(gè)對(duì)象如果在救助空間(Survivor區(qū))移動(dòng)7次還沒有被回收就放入年老代。如果設(shè)置為0的話,則年輕代對(duì)象不經(jīng)過Survivor區(qū),直接進(jìn)入年老代,對(duì)于年老代比較多的應(yīng)用,這樣做可以提高效率。如果將此值設(shè)置為一個(gè)較大值,則年輕代對(duì)象會(huì)在Survivor區(qū)進(jìn)行多次復(fù)制,這樣可以增加對(duì)象在年輕代存活時(shí)間,增加對(duì)象在年輕代即被回收的概率。

回收器選擇JVM給了三種選擇:串行收集器、并行收集器、并發(fā)收集器,但是串行收集器只適用于小數(shù)據(jù)量的情況,所以這里的選擇主要針對(duì)并行收集器和并發(fā)收集器。

默認(rèn)情況下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在啟動(dòng)時(shí)加入相應(yīng)參數(shù)。JDK5.0以后,JVM會(huì)根據(jù)當(dāng)前系統(tǒng)配置進(jìn)行智能判斷。

串行收集器 -XX:+UseSerialGC:設(shè)置串行收集器 并行收集器(吞吐量?jī)?yōu)先) -XX:+UseParallelGC:選擇垃圾收集器為并行收集器。此配置僅對(duì)年輕代有效。即上述配置下,年輕代使用并發(fā)收集,而年老代仍舊使用串行收集。 -

XX:ParallelGCThreads=20:配置并行收集器的線程數(shù),即:同時(shí)多少個(gè)線程一起進(jìn)行垃圾回收。此值最好配置與處理器數(shù)目相等。 -XX:+UseParallelOldGC:配置年老代垃圾收集方式為并行收集。JDK6.0支持對(duì)年老代并行收集。 -

XX:MaxGCPauseMillis=100:設(shè)置每次年輕代垃圾回收的最長(zhǎng)時(shí)間(單位毫秒),如果無法滿足此時(shí)間,JVM會(huì)自動(dòng)調(diào)整年輕代大小,以滿足此值。 -XX:+UseAdaptiveSizePolicy:設(shè)置此選項(xiàng)后,并行收集器會(huì)自動(dòng)選擇年輕代區(qū)大小和相應(yīng)的Survivor區(qū)比例,以達(dá)到目標(biāo)系統(tǒng)規(guī)定的最低響應(yīng)時(shí)間或者收集頻率等。 此參數(shù)建議使用并行收集器時(shí),一直打開。 并發(fā)收集器(響應(yīng)時(shí)間優(yōu)先) -XX:+UseParNewGC:設(shè)置年輕代為并發(fā)收集??膳cCMS收集同時(shí)使用。JDK5.0以上,JVM會(huì)根據(jù)系統(tǒng)配置自行設(shè)置,所以無需再設(shè)置此值。 CMS, 全稱Concurrent Low Pause Collector,是jdk1.4后期版本開始引入的新gc算法,在jdk5和jdk6中得到了進(jìn)一步改進(jìn),它的主要適合場(chǎng)景是對(duì)響應(yīng)時(shí)間的重要性需求 大于對(duì)吞吐量的要求,能夠承受垃圾回收線程和應(yīng)用線程共享處理器資源,并且應(yīng)用中存在比較多的長(zhǎng)生命周期的對(duì)象的應(yīng)用。CMS是用于對(duì)tenured generation的回收,也就是年老代的回收,目標(biāo)是盡量減少應(yīng)用的暫停時(shí)間,減少FullGC發(fā)生的幾率,利用和應(yīng)用程序線程并發(fā)的垃圾回收線程來 標(biāo)記清除年老代。 -XX:+UseConcMarkSweepGC:設(shè)置年老代為并發(fā)收集。測(cè)試中配置這個(gè)以后,-XX:NewRatio=4的配置失效了。所以,此時(shí)年輕代大小最好用-Xmn設(shè)置。 -XX:CMSFullGCsBeforeCompaction=:由于并發(fā)收集器不對(duì)內(nèi)存空間進(jìn)行壓縮、整理,所以運(yùn)行一段時(shí)間以后會(huì)產(chǎn)生“碎片”,使得運(yùn)行效率降低。此參數(shù)設(shè)置運(yùn)行次FullGC以后對(duì)內(nèi)存空間進(jìn)行壓縮、整理。 -XX:+UseCMSCompactAtFullCollection:打開對(duì)年老代的壓縮??赡軙?huì)影響性能,但是可以消除內(nèi)存碎片。 -XX:+CMSIncrementalMode:設(shè)置為增量收集模式。一般適用于單CPU情況。 -XX:CMSInitiatingOccupancyFraction=70:表示年老代空間到70%時(shí)就開始執(zhí)行CMS,確保年老代有足夠的空間接納來自年輕代的對(duì)象。 注:如果使用 throughput collector 和 concurrent low pause collector 這兩種垃圾收集器,需要適當(dāng)?shù)耐Ω邇?nèi)存大小,為多線程做準(zhǔn)備。 其它 -XX:+ScavengeBeforeFullGC:新生代GC優(yōu)先于Full GC執(zhí)行。 -XX:-DisableExplicitGC:禁止調(diào)用System.gc(),但JVM的gc仍然有效。 -XX:+MaxFDLimit:最大化文件描述符的數(shù)量限制。 -XX:+UseThreadPriorities:?jiǎn)⒂帽镜鼐€程優(yōu)先級(jí)API,即使 java.lang.Thread.setPriority() 生效,反之無效。 -XX:SoftRefLRUPolicyMSPerMB=0:“軟引用”的對(duì)象在最后一次被訪問后能存活0毫秒(默認(rèn)為1秒)。 -XX:TargetSurvivorRatio=90:允許90%的Survivor空間被占用(默認(rèn)為50%)。提高對(duì)于Survivor的使用率——超過就會(huì)嘗試?yán)厥铡?/p>

輔助信息 -XX:-CITime:打印消耗在JIT編譯的時(shí)間 -XX:ErrorFile=./hs_err_pid.log:保存錯(cuò)誤日志或者數(shù)據(jù)到指定文件中 -XX:-ExtendedDTraceProbes:開啟solaris特有的dtrace探針 -XX:HeapDumpPath=./java_pid.hprof:指定導(dǎo)出堆信息時(shí)的路徑或文件名 -XX:-HeapDumpOnOutOfMemoryError:當(dāng)首次遭遇內(nèi)存溢出時(shí)導(dǎo)出此時(shí)堆中相關(guān)信息 -XX:OnError=";":出現(xiàn)致命ERROR之后運(yùn)行自定義命令 -XX:OnOutOfMemoryError=";":當(dāng)首次遭遇內(nèi)存溢出時(shí)執(zhí)行自定義命令 -XX:-PrintClassHistogram:遇到Ctrl-Break后打印類實(shí)例的柱狀信息,與jmap -histo功能相同 -XX:-PrintConcurrentLocks:遇到Ctrl-Break后打印并發(fā)鎖的相關(guān)信息,與jstack -l功能相同 -XX:-PrintCommandLineFlags:打印在命令行中出現(xiàn)過的標(biāo)記 -XX:-PrintCompilation:當(dāng)一個(gè)方法被編譯時(shí)打印相關(guān)信息 -XX:-PrintGC:每次GC時(shí)打印相關(guān)信息 -XX:-PrintGC Details:每次GC時(shí)打印詳細(xì)信息 -XX:-PrintGCTimeStamps:打印每次GC的時(shí)間戳 -XX:-TraceClassLoading:跟蹤類的加載信息 -XX:-TraceClassLoadingPreorder:跟蹤被引用到的所有類的加載信息 -XX:-TraceClassResolution:跟蹤常量池 -XX:-TraceClassUnloading:跟蹤類的卸載信息 -XX:-TraceLoaderConstraints:跟蹤類加載器約束的相關(guān)信息

JVM服務(wù)調(diào)優(yōu)實(shí)戰(zhàn) 服務(wù)器:8 cup, 8G mem e.g. java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0

調(diào)優(yōu)方案: -Xmx5g:設(shè)置JVM最大可用內(nèi)存為5G。 -Xms5g:設(shè)置JVM初始內(nèi)存為5G。此值可以設(shè)置與-Xmx相同,以避免每次垃圾回收完成后JVM重新分配內(nèi)存。 -Xmn2g:設(shè)置年輕代大小為2G。整個(gè)堆內(nèi)存大小 = 年輕代大小 + 年老代大小 + 持久代大小 。持久代一般固定大小為64m,所以增大年輕代后,將會(huì)減小年老代大小。此值對(duì)系統(tǒng)性能影響較大,Sun官方推薦配置為整個(gè)堆的3/8。 -XX:+UseParNewGC:設(shè)置年輕代為并行收集??膳cCMS收集同時(shí)使用。JDK5.0以上,JVM會(huì)根據(jù)系統(tǒng)配置自行設(shè)置,所以無需再設(shè)置此值。 -XX:ParallelGCThreads=8:配置并行收集器的線程數(shù),即:同時(shí)多少個(gè)線程一起進(jìn)行垃圾回收。此值最好配置與處理器數(shù)目相等。 -XX:SurvivorRatio=6:設(shè)置年輕代中Eden區(qū)與Survivor區(qū)的大小比值。根據(jù)經(jīng)驗(yàn)設(shè)置為6,則兩個(gè)Survivor區(qū)與一個(gè)Eden區(qū)的比值為2:6,一個(gè)Survivor區(qū)占整個(gè)年輕代的1/8。 -XX:MaxTenuringThreshold=30:設(shè)置垃圾最大年齡(次數(shù))。如果設(shè)置為0的話,則年輕代對(duì)象不經(jīng)過Survivor區(qū)直接進(jìn)入年老代。對(duì)于年老代比較多的應(yīng)用,可以提高效率。如果將此值 設(shè)置為一個(gè)較大值,則年輕代對(duì)象會(huì)在Survivor區(qū)進(jìn)行多次復(fù)制,這樣可以增加對(duì)象再年輕代的存活時(shí)間,增加在年輕代即被回收的概率。設(shè)置為30表示 一個(gè)對(duì)象如果在Survivor空間移動(dòng)30次還沒有被回收就放入年老代。 -XX:+UseConcMarkSweepGC:設(shè)置年老代為并發(fā)收集。測(cè)試配置這個(gè)參數(shù)以后,參數(shù)-XX:NewRatio=4就失效了,所以,此時(shí)年輕代大小最好用-Xmn設(shè)置,因此這個(gè)參數(shù)不建議使用。

參考資料 - JVM堆內(nèi)存的分代 虛擬機(jī)的堆內(nèi)存共劃分為三個(gè)代:年輕代(Young Generation)、年老代(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java類的類信息,與垃圾收集器要收集的Java對(duì)象關(guān)系不大。所以,年輕代和年老代的劃分才是對(duì)垃圾 收集影響比較大的。

年輕代 

所有新生成的對(duì)象首先都是放在年輕代的。年輕代的目標(biāo)就是盡可能快速的收集掉那些生命周期短的對(duì)象。年輕代分三個(gè)區(qū)。一個(gè)Eden區(qū),兩個(gè) Survivor區(qū)(一般而言)。 大部分對(duì)象在Eden區(qū)中生成。當(dāng)Eden區(qū)滿時(shí),還存活的對(duì)象將被復(fù)制到Survivor區(qū)(兩個(gè)中的一個(gè)),當(dāng)一個(gè)Survivor區(qū)滿 時(shí),此區(qū)的存活對(duì)象將被復(fù)制到另外一個(gè)Survivor區(qū),當(dāng)另一個(gè)Survivor區(qū)也滿了的時(shí)候,從前一個(gè)Survivor區(qū)復(fù)制過來的并且此時(shí)還存 活的對(duì)象,將被復(fù)制“年老區(qū)(Tenured)”。 需要注意,兩個(gè)Survivor區(qū)是對(duì)稱的,沒先后關(guān)系,所以同一個(gè)Survivor區(qū)中可能同時(shí)存在從Eden區(qū)復(fù)制過來對(duì)象,和從另一個(gè) Survivor區(qū)復(fù)制過來的對(duì)象;而復(fù)制到年老區(qū)的只有從前一個(gè)Survivor區(qū)(相對(duì)的)過來的對(duì)象。而且,Survivor區(qū)總有一個(gè)是空的。特 殊的情況下,根據(jù)程序需要,Survivor區(qū)是可以配置為多個(gè)的(多于兩個(gè)),這樣可以增加對(duì)象在年輕代中的存在時(shí)間,減少被放到年老代的可能。

年老代 

在年輕代中經(jīng)歷了N(可配置)次垃圾回收后仍然存活的對(duì)象,就會(huì)被放到年老代中。因此,可以認(rèn)為年老代中存放的都是一些生命周期較長(zhǎng)的對(duì)象。

持久代 用于存放靜態(tài)數(shù)據(jù),如 Java Class, Method 等。持久代對(duì)垃圾回收沒有顯著影響,但是有些應(yīng)用可能動(dòng)態(tài)生成或者調(diào)用一些Class,例如 Hibernate 等,在這種時(shí)候需要設(shè)置一個(gè)比較大的持久代空間來存放這些運(yùn)行過程中動(dòng)態(tài)增加的類型。持久代大小通過 -XX:MaxPermSize= 進(jìn)行設(shè)置。 

目前了解的GC收集器就是這么多了,如果有哪位有緣的朋友看到了這篇文章,恰好有更好的GC收集器推薦,歡迎留言交流。


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI