內(nèi)存溢出的幾種原因和解決辦法+jvm運(yùn)行參數(shù)配置詳解

本文抄自:https://blog.csdn.net/cp_panda_5/article/details/79613870,請(qǐng)讀者移步查看原文,本文是為防止該文失效同時(shí)為了本人方便查看,做的備份。

對(duì)于JVM的內(nèi)存寫(xiě)過(guò)的文章已經(jīng)有點(diǎn)多了,而且有點(diǎn)爛了,不過(guò)說(shuō)那么多大多數(shù)在解決OOM的情況,于此,本文就只闡述這個(gè)內(nèi)容,攜帶一些分析和理解和部分?jǐn)U展內(nèi)容,也就是JVM宕機(jī)中的一些問(wèn)題,OK,下面說(shuō)下OOM的常見(jiàn)情況:
第一類內(nèi)存溢出,也是大家認(rèn)為最多,第一反應(yīng)認(rèn)為是的內(nèi)存溢出,就是堆棧溢出:

那什么樣的情況就是堆棧溢出呢?當(dāng)你看到下面的關(guān)鍵字的時(shí)候它就是堆棧溢出了:

java.lang.OutOfMemoryError: ......java heap space.....

也就是當(dāng)你看到heap相關(guān)的時(shí)候就肯定是堆棧溢出了,此時(shí)如果代碼沒(méi)有問(wèn)題的情況下,適當(dāng)調(diào)整-Xmx和-Xms是可以避免的,不過(guò)一定是代碼沒(méi)有問(wèn)題的前提,為什么會(huì)溢出呢,要么代碼有問(wèn)題,要么訪問(wèn)量太多并且每個(gè)訪問(wèn)的時(shí)間太長(zhǎng)或者數(shù)據(jù)太多,導(dǎo)致數(shù)據(jù)釋放不掉,因?yàn)槔厥掌魇且业侥切┦抢拍芑厥?,這里它不會(huì)認(rèn)為這些東西是垃圾,自然不會(huì)去回收了;主意這個(gè)溢出之前,可能系統(tǒng)會(huì)提前先報(bào)錯(cuò)關(guān)鍵字為:

java.lang.OutOfMemoryError:GC over head limit exceeded

這種情況是當(dāng)系統(tǒng)處于高頻的GC狀態(tài),而且回收的效果依然不佳的情況,就會(huì)開(kāi)始報(bào)這個(gè)錯(cuò)誤,這種情況一般是產(chǎn)生了很多不可以被釋放的對(duì)象,有可能是引用使用不當(dāng)導(dǎo)致,或申請(qǐng)大對(duì)象導(dǎo)致,但是java heap space的內(nèi)存溢出有可能提前不會(huì)報(bào)這個(gè)錯(cuò)誤,也就是可能內(nèi)存就直接不夠?qū)е?,而不是高頻GC,因?yàn)間c時(shí)間消耗的較多。解決這種問(wèn)題兩種方法是,增加參數(shù),-XX:-UseGCOverheadLimit,關(guān)閉這個(gè)特性,同時(shí)增加heap大小,-Xmx1024m。

第二類內(nèi)存溢出,PermGen的溢出,或者PermGen 滿了的提示,你會(huì)看到這樣的關(guān)鍵字:

關(guān)鍵信息為:

java.lang.OutOfMemoryError: PermGen space

原因:系統(tǒng)的代碼非常多或引用的第三方包非常多、或代碼中使用了大量的常量、或通過(guò)intern注入常量、或者通過(guò)動(dòng)態(tài)代碼加載等方法,導(dǎo)致常量池的膨脹,雖然JDK 1.5以后可以通過(guò)設(shè)置對(duì)永久帶進(jìn)行回收,但是我們希望的是這個(gè)地方是不做GC的,它夠用就行,所以一般情況下盡量少做類似的操作,所以在面對(duì)這種情況常用的手段是:增加-XX:PermSize和-XX:MaxPermSize的大小。

第三類內(nèi)存溢出:在使用ByteBuffer中的allocateDirect()的時(shí)候會(huì)用到,很多javaNIO的框架中被封裝為其他的方法

溢出關(guān)鍵字:

java.lang.OutOfMemoryError: Direct buffer memory
如果你在直接或間接使用了ByteBuffer中的allocateDirect方法的時(shí)候,而不做clear的時(shí)候就會(huì)出現(xiàn)類似的問(wèn)題,常規(guī)的引用程序IO輸出存在一個(gè)內(nèi)核態(tài)與用戶態(tài)的轉(zhuǎn)換過(guò)程,也就是對(duì)應(yīng)直接內(nèi)存與非直接內(nèi)存,如果常規(guī)的應(yīng)用程序你要將一個(gè)文件的內(nèi)容輸出到客戶端需要通過(guò)OS的直接內(nèi)存轉(zhuǎn)換拷貝到程序的非直接內(nèi)存(也就是heap中),然后再輸出到直接內(nèi)存由操作系統(tǒng)發(fā)送出去,而直接內(nèi)存就是由OS和應(yīng)用程序共同管理的,而非直接內(nèi)存可以直接由應(yīng)用程序自己控制的內(nèi)存,jvm垃圾回收不會(huì)回收掉直接內(nèi)存這部分的內(nèi)存,所以要注意了哦。

如果經(jīng)常有類似的操作,可以考慮設(shè)置參數(shù):-XX:MaxDirectMemorySize

第四類內(nèi)存溢出錯(cuò)誤:

溢出關(guān)鍵字:

java.lang.StackOverflowError

這個(gè)參數(shù)直接說(shuō)明一個(gè)內(nèi)容,就是-Xss太小了,我們申請(qǐng)很多局部調(diào)用的棧針等內(nèi)容是存放在用戶當(dāng)前所持有的線程中的,線程在jdk 1.4以前默認(rèn)是256K,1.5以后是1M,如果報(bào)這個(gè)錯(cuò),只能說(shuō)明-Xss設(shè)置得太小,當(dāng)然有些廠商的JVM不是這個(gè)參數(shù),本文僅僅針對(duì)Hotspot VM而已;不過(guò)在有必要的情況下可以對(duì)系統(tǒng)做一些優(yōu)化,使得-Xss的值是可用的。

第五類內(nèi)存溢出錯(cuò)誤:

溢出關(guān)鍵字:

java.lang.OutOfMemoryError: unable to create new native thread

上面第四種溢出錯(cuò)誤,已經(jīng)說(shuō)明了線程的內(nèi)存空間,其實(shí)線程基本只占用heap以外的內(nèi)存區(qū)域,也就是這個(gè)錯(cuò)誤說(shuō)明除了heap以外的區(qū)域,無(wú)法為線程分配一塊內(nèi)存區(qū)域了,這個(gè)要么是內(nèi)存本身就不夠,要么heap的空間設(shè)置得太大了,導(dǎo)致了剩余的內(nèi)存已經(jīng)不多了,而由于線程本身要占用內(nèi)存,所以就不夠用了,說(shuō)明了原因,如何去修改,不用我多說(shuō),你懂的。

第六類內(nèi)存溢出:

溢出關(guān)鍵字

java.lang.OutOfMemoryError: request {} byte for {}out of swap

這類錯(cuò)誤一般是由于地址空間不夠而導(dǎo)致。

六大類常見(jiàn)溢出已經(jīng)說(shuō)明JVM中99%的溢出情況,要逃出這些溢出情況非常困難,除非一些很怪異的故障問(wèn)題會(huì)發(fā)生,比如由于物理內(nèi)存的硬件問(wèn)題,導(dǎo)致了code cache的錯(cuò)誤(在由byte code轉(zhuǎn)換為native code的過(guò)程中出現(xiàn),但是概率極低),這種情況內(nèi)存 會(huì)被直接crash掉,類似還有swap的頻繁交互在部分系統(tǒng)中會(huì)導(dǎo)致系統(tǒng)直接被crash掉,OS地址空間不夠的話,系統(tǒng)根本無(wú)法啟動(dòng),呵呵;JNI的濫用也會(huì)導(dǎo)致一些本地內(nèi)存無(wú)法釋放的問(wèn)題,所以盡量避開(kāi)JNI;socket連接數(shù)據(jù)打開(kāi)過(guò)多的socket也會(huì)報(bào)類似:IOException: Too many open files等錯(cuò)誤信息。

JNI就不用多說(shuō)了,盡量少用,除非你的代碼太牛B了,我無(wú)話可說(shuō),呵呵,這種內(nèi)存如果沒(méi)有在被調(diào)用的語(yǔ)言內(nèi)部將內(nèi)存釋放掉(如C語(yǔ)言),那么在進(jìn)程結(jié)束前這些內(nèi)存永遠(yuǎn)釋放不掉,解決辦法只有一個(gè)就是將進(jìn)程kill掉。

另外GC本身是需要內(nèi)存空間的,因?yàn)樵谶\(yùn)算和中間數(shù)據(jù)轉(zhuǎn)換過(guò)程中都需要有內(nèi)存,所以你要保證GC的時(shí)候有足夠的內(nèi)存哦,如果沒(méi)有的話GC的過(guò)程將會(huì)非常的緩慢。

順便這里就提及一些新的CMS GC的內(nèi)容和策略(有點(diǎn)亂,每次寫(xiě)都很亂,但是能看多少看多少吧):

首先我再寫(xiě)一次一前博客中的已經(jīng)寫(xiě)過(guò)的內(nèi)容,就是很多參數(shù)沒(méi)啥建議值,建議值是自己在現(xiàn)場(chǎng)根據(jù)實(shí)際情況科學(xué)計(jì)算和測(cè)試得到的綜合效果,建議值沒(méi)有絕對(duì)好的,而且默認(rèn)值很多也是有問(wèn)題的,因?yàn)椴煌陌姹竞蛷S商都有很大的區(qū)別,默認(rèn)值沒(méi)有永久都是一樣的,就像-Xss參數(shù)的變化一樣,要看到你當(dāng)前的java程序heap的大致情況可以這樣看看(以下參數(shù)是隨便設(shè)置的,并不是什么默認(rèn)值):

$sudo jmap -heap pgrep java
Attaching to process ID 4280, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 19.1-b02

using thread-local object allocation.
Parallel GC with 8 thread(s)

Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 1073741824 (1024.0MB)
NewSize = 134217728 (128.0MB)
MaxNewSize = 134217728 (128.0MB)
OldSize = 5439488 (5.1875MB)
NewRatio = 2
SurvivorRatio = 8
PermSize = 134217728 (128.0MB)
MaxPermSize = 268435456 (256.0MB)

Heap Usage:
PS Young Generation
Eden Space:
capacity = 85721088 (81.75MB)
used = 22481312 (21.439849853515625MB)
free = 63239776 (60.310150146484375MB)
26.22611602876529% used
From Space:
capacity = 24051712 (22.9375MB)
used = 478488 (0.45632171630859375MB)
free = 23573224 (22.481178283691406MB)
1.9894134770946867% used
To Space:
capacity = 24248320 (23.125MB)
used = 0 (0.0MB)
free = 24248320 (23.125MB)
0.0% used
PS Old Generation
capacity = 939524096 (896.0MB)
used = 16343864 (15.586723327636719MB)
free = 923180232 (880.4132766723633MB)
1.7395896571023124% used
PS Perm Generation
capacity = 134217728 (128.0MB)
used = 48021344 (45.796722412109375MB)
free = 86196384 (82.20327758789062MB)
35.77868938446045% used

付:sudo是需要拿到管理員權(quán)限,如果你的系統(tǒng)權(quán)限很大那么就不需要了,最后的grep java那個(gè)內(nèi)容如果不對(duì),可以直接通過(guò)jps或者ps命令將和java相關(guān)的進(jìn)程號(hào)直接寫(xiě)進(jìn)去,如:java -map 4280,這個(gè)參數(shù)其實(shí)完全可以通過(guò)jstat工具來(lái)替代,而且看到的效果更加好,這個(gè)參數(shù)在線上應(yīng)用中,盡量少用(尤其是高并發(fā)的應(yīng)用中),可能會(huì)觸發(fā)JVM的bug,導(dǎo)致應(yīng)用掛起;在jvm 1.6u14后可以編寫(xiě)任意一段程序,然后在運(yùn)行程序的時(shí)候,增加參數(shù)為:-XX:+PrintFlagsFinal來(lái)輸出當(dāng)前JVM中運(yùn)行時(shí)的參數(shù)值,或者通過(guò)jinfo來(lái)查看,jinfo是非常強(qiáng)大的工具,可以對(duì)部分參數(shù)進(jìn)行動(dòng)態(tài)修改,當(dāng)然內(nèi)存相關(guān)的東西是不能修改的,只能增加一些不是很相關(guān)的參數(shù),有關(guān)JVM的工具使用,后續(xù)文章中如果有機(jī)會(huì)我們?cè)賮?lái)探討,不是本文的重點(diǎn);補(bǔ)充:關(guān)于參數(shù)的默認(rèn)值對(duì)不同的JVM版本、不同的廠商、運(yùn)行于不同的環(huán)境(一般和位數(shù)有關(guān)系)默認(rèn)值會(huì)有區(qū)別。

OK,再說(shuō)下反復(fù)的一句,沒(méi)有必要的話就不要亂設(shè)置參數(shù),參數(shù)不是拿來(lái)玩的,默認(rèn)的參數(shù)對(duì)于這門JDK都是有好處的,關(guān)鍵是否適合你的應(yīng)用場(chǎng)景,一般來(lái)講你常規(guī)的只需要設(shè)置以下幾個(gè)參數(shù)就可以了:

-server 表示為服務(wù)器端,會(huì)提供很多服務(wù)器端默認(rèn)的配置,如并行回收,而服務(wù)器上一般這個(gè)參數(shù)都是默認(rèn)的,所以都是可以省掉,與之對(duì)應(yīng)的還有一個(gè)-client參數(shù),一般在64位機(jī)器上,JVM是默認(rèn)啟動(dòng)-server參數(shù),也就是默認(rèn)啟動(dòng)并行GC的,但是是ParallelGC而不是ParallelOldGC,兩者算法不同(后面會(huì)簡(jiǎn)單說(shuō)明下),而比較特殊的是windows 32位上默認(rèn)是-client,這兩個(gè)的區(qū)別不僅僅是默認(rèn)的參數(shù)不一樣,在jdk包下的jre包下一般會(huì)包含client和server包,下面分別對(duì)應(yīng)啟動(dòng)的動(dòng)態(tài)鏈接庫(kù),而真正看到的java、javac等相關(guān)命令指示一個(gè)啟動(dòng)導(dǎo)向,它只是根據(jù)命令找到對(duì)應(yīng)的JVM并傳入jvm中進(jìn)行啟動(dòng),也就是看到的java.exe這些文件并不是jvm;說(shuō)了這么多,最終總結(jié)一下就是,-server和-client就是完全不同的兩套VM,一個(gè)用于桌面應(yīng)用,一個(gè)用于服務(wù)器的。

-Xmx 為Heap區(qū)域的最大值

-Xms 為Heap區(qū)域的初始值,線上環(huán)境需要與-Xmx設(shè)置為一致,否則capacity的值會(huì)來(lái)回飄動(dòng),飄得你心曠神怡,你懂的。

-Xss(或-ss) 這個(gè)其實(shí)也是可以默認(rèn)的,如果你真的覺(jué)得有設(shè)置的必要,你就改下吧,1.5以后是1M的默認(rèn)大小(指一個(gè)線程的native空間),如果代碼不多,可以設(shè)置小點(diǎn)來(lái)讓系統(tǒng)可以接受更大的內(nèi)存。注意,還有一個(gè)參數(shù)是-XX:ThreadStackSize,這兩個(gè)參數(shù)在設(shè)置的過(guò)程中如果都設(shè)置是有沖突的,一般按照J(rèn)VM常理來(lái)說(shuō),誰(shuí)設(shè)置在后面,就以誰(shuí)為主,但是最后發(fā)現(xiàn)如果是在1.6以上的版本,-Xss設(shè)置在后面的確都是以-Xss為主,但是要是-XX:ThreadStackSize設(shè)置在后面,主線程還是為-Xss為主,而其它線程以-XX:ThreadStackSize為主,主線程做了一個(gè)特殊判定處理;單獨(dú)設(shè)置都是以本身為主,-Xss不設(shè)置也不會(huì)采用其默認(rèn)值,除非兩個(gè)都不設(shè)置會(huì)采用-Xss的默認(rèn)值。另外這個(gè)參數(shù)針對(duì)于hotspot的vm,在IBM的jvm中,還有一個(gè)參數(shù)為-Xoss,主要原因是IBM在對(duì)棧的處理上有操作數(shù)棧和方法棧等各種不同的棧種類,而hotspot不管是什么棧都放在一個(gè)私有的線程內(nèi)部的,不區(qū)分是什么棧,所以只需要設(shè)置一個(gè)參數(shù),而IBM的J9不是這樣的;有關(guān)棧上的細(xì)節(jié),后續(xù)我們有機(jī)會(huì)專門寫(xiě)文章來(lái)說(shuō)明。

-XX:PermSize與-XX:MaxPermSize兩個(gè)包含了class的裝載的位置,或者說(shuō)是方法區(qū)(但不是本地方法區(qū)),在Hotspot默認(rèn)情況下為64M,主意全世界的JVM只有hostpot的VM才有Perm的區(qū)域,或者說(shuō)只有hotspot才有對(duì)用戶可以設(shè)置的這塊區(qū)域,其他的JVM都沒(méi)有,其實(shí)并不是沒(méi)有這塊區(qū)域,而是這塊區(qū)域沒(méi)有讓用戶來(lái)設(shè)置,其實(shí)這塊區(qū)域本身也不應(yīng)該讓用戶來(lái)設(shè)置,我們也沒(méi)有一個(gè)明確的說(shuō)法這塊空間必須要設(shè)置多大,都是拍腦袋設(shè)置一個(gè)數(shù)字,如果發(fā)布到線上看下如果用得比較多,就再多點(diǎn),如果用的少,就減少點(diǎn),而這塊區(qū)域和性能關(guān)鍵沒(méi)有多大關(guān)系,只要能裝下就OK,并且時(shí)不時(shí)會(huì)因?yàn)镻erm不夠而導(dǎo)致Full GC,所以交給開(kāi)發(fā)者來(lái)調(diào)節(jié)這個(gè)參數(shù)不知道是怎么想的;所以O(shè)racle將在新一代JVM中將這個(gè)區(qū)域徹底刪掉,也就是對(duì)用戶透明,G1的如果真正穩(wěn)定起來(lái),以后JVM的啟動(dòng)參數(shù)將會(huì)非常簡(jiǎn)單,而且理論上管理再大的內(nèi)存也是沒(méi)有問(wèn)題的,其實(shí)G1(garbage first,一種基于region的垃圾收集回收器)已經(jīng)在hotspot中開(kāi)始有所試用,不過(guò)目前效果不好,還不如CMS呢,所以只是試用,G1已經(jīng)作為ORACLE對(duì)JVM研發(fā)的最高重點(diǎn),CMS自現(xiàn)在最高版本后也不再有新功能(可以修改bug),該項(xiàng)目已經(jīng)進(jìn)行5年,尚未發(fā)布正式版,CMS是四五年前發(fā)布的正式版,但是是最近一兩年才開(kāi)始穩(wěn)定,而G1的復(fù)雜性將會(huì)遠(yuǎn)遠(yuǎn)超越CMS,所以要真正使用上G1還有待考察,全世界目前只有IBM J9真正實(shí)現(xiàn)了G1論文中提到的思想(論文于05年左右發(fā)表),IBM已經(jīng)將J9應(yīng)用于websphere中,但是并不代表這是全世界最好的jvm,全世界最好的jvm是Azul(無(wú)停頓垃圾回收算法和一個(gè)零開(kāi)銷的診斷/監(jiān)控工具),幾乎可以說(shuō)這個(gè)jvm是沒(méi)有暫停的,在全世界很多頂尖級(jí)的公司使用,不過(guò)價(jià)格非常貴,不能直接使用,目前這個(gè)jvm的主導(dǎo)者在研究JRockit,而目前hotspot和JRockit都是Oracle的,所以他們可能會(huì)合并,所以我們應(yīng)該對(duì)JVM的性能充滿信心。

也就是說(shuō)你常用的情況下只需要設(shè)置4個(gè)參數(shù)就OK了,除非你的應(yīng)用有些特殊,否則不要亂改,那么來(lái)看看一些其他情況的參數(shù)吧:

先來(lái)看個(gè)不大常用的,就是大家都知道JVM新的對(duì)象應(yīng)該說(shuō)幾乎百分百的在Eden里面,除非Eden真的裝不下,我們不考慮這種變態(tài)的問(wèn)題,因?yàn)榫€上環(huán)境Eden區(qū)域都是不小的,來(lái)降低GC的次數(shù)以及全局 GC的概率;而JVM習(xí)慣將內(nèi)存按照較為連續(xù)的位置進(jìn)行分配,這樣使得有足夠的內(nèi)存可以被分配,減少碎片,那么對(duì)于內(nèi)存最后一個(gè)位置必然就有大量的征用問(wèn)題,JVM在高一點(diǎn)的版本里面提出了為每個(gè)線程分配一些私有的區(qū)域來(lái)做來(lái)解決這個(gè)問(wèn)題,而1.5后的版本還可以動(dòng)態(tài)管理這些區(qū)域,那么如何自己設(shè)置和查看這些區(qū)域呢,看下英文全稱為:Thread Local Allocation Buffer,簡(jiǎn)稱就是:TLAB,即內(nèi)存本地的持有的buffer,設(shè)置參數(shù)有:

-XX:+UseTLAB 啟用這種機(jī)制的意思
-XX:TLABSize=<size in kb> 設(shè)置大小,也就是本地線程中的私有區(qū)域大?。ㄖ挥羞@個(gè)區(qū)域放不下才會(huì)到Eden中去申請(qǐng))。
-XX:+ResizeTLAB 是否啟動(dòng)動(dòng)態(tài)修改

這幾個(gè)參數(shù)在多CPU下非常有用。

-XX:+PrintTLAB 可以輸出TLAB的內(nèi)容。

下面再閑扯些其它的參數(shù):

如果你需要對(duì)Yong區(qū)域進(jìn)行并行回收應(yīng)該如何修改呢?在jdk1.5以后可以使用參數(shù):

-XX:+UseParNewGC

注意: 與它沖突的參數(shù)是:-XX:+UseParallelOldGC和-XX:+UseSerialGC,如果需要用這個(gè)參數(shù),又想讓整個(gè)區(qū)域是并行回收的,那么就使用-XX:+UseConcMarkSweepGC參數(shù)來(lái)配合,其實(shí)這個(gè)參數(shù)在使用了CMS后,默認(rèn)就會(huì)啟動(dòng)該參數(shù),也就是這個(gè)參數(shù)在CMS GC下是無(wú)需設(shè)置的,后面會(huì)提及到這些參數(shù)。

默認(rèn)服務(wù)器上的對(duì)Full并行GC策略為(這個(gè)時(shí)候Yong空間回收的時(shí)候啟動(dòng)PSYong算法,也是并行回收的):

-XX:+UseParallelGC

另外,在jdk1.5后出現(xiàn)一個(gè)新的參數(shù)如下,這個(gè)對(duì)Yong的回收算法和上面一樣,對(duì)Old區(qū)域會(huì)有所區(qū)別,上面對(duì)Old回收的過(guò)程中會(huì)做一個(gè)全局的Compact,也就是全局的壓縮操作,而下面的算法是局部壓縮,為什么要局部壓縮呢?是因?yàn)镴VM發(fā)現(xiàn)每次壓縮后再邏輯上數(shù)據(jù)都在Old區(qū)域的左邊位置,申請(qǐng)的時(shí)候從左向右申請(qǐng),那么生命力越長(zhǎng)的對(duì)象就一般是靠左的,所以它認(rèn)為左邊的對(duì)象就是生命力很強(qiáng),而且較為密集的,所以它針對(duì)這種情況進(jìn)行部分密集,但是這兩種算法mark階段都是會(huì)暫停的,而且存活的對(duì)象越多活著的越多;而ParallelOldGC會(huì)進(jìn)行部分壓縮算法(主意一點(diǎn),最原始的copy算法是不需要經(jīng)過(guò)mark階段,因?yàn)橹恍枰业揭粋€(gè)或活著的就只需要做拷貝就可以,而Yong區(qū)域借用了Copy算法,只是唯一的區(qū)別就是傳統(tǒng)的copy算法是采用兩個(gè)相同大小的內(nèi)存來(lái)拷貝,浪費(fèi)空間為50%,所以分代的目標(biāo)就是想要實(shí)現(xiàn)很多優(yōu)勢(shì)所在,認(rèn)為新生代85%以上的對(duì)象都應(yīng)該是死掉的,所以S0和S1一般并不是很大),該算法為jdk 1.5以后對(duì)于絕大部分應(yīng)用的最佳選擇。

-XX:+UseParallelOldGC

-XX:ParallelGCThread=12:并行回收的線程數(shù),最好根據(jù)實(shí)際情況而定,因?yàn)榫€程多往往存在征用調(diào)度和上下文切換的開(kāi)銷;而且也并非CPU越多線程數(shù)也可以設(shè)置越大,一般設(shè)置為12就再增加用處也不大,主要是算法本身內(nèi)部的征用會(huì)導(dǎo)致其線程的極限就是這樣。

設(shè)置Yong區(qū)域大?。?/p>

-Xmn Yong區(qū)域的初始值和最大值一樣大

-XX:NewSize和-XX:MaxNewSize如果設(shè)置以為一樣大就是和-Xmn,在JRockit中會(huì)動(dòng)態(tài)變化這些參數(shù),根據(jù)實(shí)際情況有可能會(huì)變化出兩個(gè)Yong區(qū)域,或者沒(méi)有Yong區(qū)域,有些時(shí)候會(huì)生出來(lái)一個(gè)半長(zhǎng)命對(duì)象區(qū)域;這里除了這幾個(gè)參數(shù)外,還有一個(gè)參數(shù)是NewRatio是設(shè)置Old/Yong的倍數(shù)的,這幾個(gè)參數(shù)都是有沖突的,服務(wù)器端建議是設(shè)置-Xmn就可以了,如果幾個(gè)參數(shù)全部都有設(shè)置,-Xmn和-XX:NewSize與-XX:MaxNewSize將是誰(shuí)設(shè)置在后面,以誰(shuí)的為準(zhǔn),而-XX:NewSize -XX:MaxNewSize與-XX:NewRatio時(shí),那么參數(shù)設(shè)置的結(jié)果可能會(huì)以下這樣的(jdk 1.4.1后):

min(MaxNewSize,max(NewSize, heap/(NewRatio+1)))

-XX:NewRatio為Old區(qū)域?yàn)閅ong的多少倍,間接設(shè)置Yong的大小,1.6中如果使用此參數(shù),則默認(rèn)會(huì)在適當(dāng)時(shí)候被動(dòng)態(tài)調(diào)整,具體請(qǐng)看下面參數(shù)UseAdaptiveSizepollcy 的說(shuō)明。

三個(gè)參數(shù)不要同時(shí)設(shè)置,因?yàn)槎际窃O(shè)置Yong的大小的。

-XX:SurvivorRatio:該參數(shù)為Eden與兩個(gè)求助空間之一的比例,注意Yong的大小等價(jià)于Eden + S0 + S1,S0和S1的大小是等價(jià)的,這個(gè)參數(shù)為Eden與其中一個(gè)S區(qū)域的大小比例,如參數(shù)為8,那么Eden就占用Yong的80%,而S0和S1分別占用10%。

以前的老版本有一個(gè)參數(shù)為:-XX:InitialSurivivorRatio,如果不做任何設(shè)置,就會(huì)以這個(gè)參數(shù)為準(zhǔn),這個(gè)參數(shù)的默認(rèn)值就是8,不過(guò)這個(gè)參數(shù)并不是Eden/Survivor的大小,而是Yong/Survivor,所以所以默認(rèn)值8,代表每一個(gè)S區(qū)域的空間大小為Yong區(qū)域的12.5%而不是10%。另外順便提及一下,每次大家看到GC日志的時(shí)候,GC日志中的每個(gè)區(qū)域的最大值,其中Yong的空間最大值,始終比設(shè)置的Yong空間的大小要小一點(diǎn),大概是小12.5%左右,那是因?yàn)槊看慰捎每臻g為Eden加上一個(gè)Survivor區(qū)域的大小,而不是整個(gè)Yong的大小,因?yàn)榭捎每臻g每次最多是這樣大,兩個(gè)Survivor區(qū)域始終有一塊是空的,所以不會(huì)加上兩個(gè)來(lái)計(jì)算。

-XX:MaxTenuringThreshold=15:在正常情況下,新申請(qǐng)的對(duì)象在Yong區(qū)域發(fā)生多少次GC后就會(huì)被移動(dòng)到Old(非正常就是S0或S1放不下或者不太可能出現(xiàn)的Eden都放不下的對(duì)象),這個(gè)參數(shù)一般不會(huì)超過(guò)16(因?yàn)橛?jì)數(shù)器從0開(kāi)始計(jì)數(shù),所以設(shè)置為15的時(shí)候相當(dāng)于生命周期為16)。

要查看現(xiàn)在的這個(gè)值的具體情況,可以使用參數(shù):-XX:+PrintTenuringDistribution

通過(guò)上面的jmap應(yīng)該可以看出我的機(jī)器上的MinHeapFreeRatio和MaxHeapFreeRatio分別為40個(gè)70,也就是大家經(jīng)常說(shuō)的在GC后剩余空間小于40%時(shí)capacity開(kāi)始增大,而大于70%時(shí)減小,由于我們不希望讓它移動(dòng),所以這兩個(gè)參數(shù)幾乎沒(méi)有意義,如果你需要設(shè)置就設(shè)置參數(shù)為:

-XX:MinHeapFreeRatio=40
-XX:MaxHeapFreeRatio=70

JDK 1.6后有一個(gè)動(dòng)態(tài)調(diào)節(jié)板塊的,當(dāng)然如果你的每一個(gè)板塊都是設(shè)置固定值,這個(gè)參數(shù)也沒(méi)有用,不過(guò)如果是非固定的,建議還是不要?jiǎng)討B(tài)調(diào)整,默認(rèn)是開(kāi)啟的,建議將其關(guān)掉,參數(shù)為:

-XX:+UseAdaptiveSizepollcy 建議使用-XX:-UseAdaptiveSizepollcy關(guān)掉,為什么當(dāng)你的參數(shù)設(shè)置了NewRatio、Survivor、MaxTenuringThreshold這幾個(gè)參數(shù)如果在啟動(dòng)了動(dòng)態(tài)更新情況下,是無(wú)效的,當(dāng)然如果你設(shè)置-Xmn是有效的,但是如果設(shè)置的比例的話,初始化可能會(huì)按照你的參數(shù)去運(yùn)行,不過(guò)運(yùn)行過(guò)程中會(huì)通過(guò)一定的算法動(dòng)態(tài)修改,監(jiān)控中你可能會(huì)發(fā)現(xiàn)這些參數(shù)會(huì)發(fā)生改變,甚至于S0和S1的大小不一樣。

如果啟動(dòng)了這個(gè)參數(shù),又想要跟蹤變化,那么就使用參數(shù):-XX:+PrintAdaptiveSizePolicy

上面已經(jīng)提到,javaNIO中通過(guò)Direct內(nèi)存來(lái)提高性能,這個(gè)區(qū)域的大小默認(rèn)是64M,在適當(dāng)?shù)膱?chǎng)景可以設(shè)置大一些。

-XX:MaxDirectMemorySize

一個(gè)不太常用的參數(shù):

-XX:+ScavengeBeforeFullGC 默認(rèn)是開(kāi)啟狀態(tài),在full GC前先進(jìn)行minor GC。

對(duì)于java堆中如果要設(shè)置大頁(yè)內(nèi)存,可以通過(guò)設(shè)置參數(shù):

付:此參數(shù)必須在操作系統(tǒng)的內(nèi)核支持的基礎(chǔ)上,需要在OS級(jí)別做操作為:

echo 1024 > /proc/sys/vm/nr_hugepages

echo 2147483647 > /proc/sys/kernel/shmmax

-XX:+UseLargePages

-XX:LargePageSizeInBytes

此時(shí)整個(gè)JVM都將在這塊內(nèi)存中,否則全部不在這塊內(nèi)存中。

javaIO的臨時(shí)目錄設(shè)置

-Djava.io.tmpdir

jstack會(huì)去尋找/tmp/hsperfdata_admin下去尋找與進(jìn)程號(hào)相同的文件,32位機(jī)器上是沒(méi)有問(wèn)題的,64為機(jī)器的是有BUG的,在jdk 1.6u23版本中已經(jīng)修復(fù)了這個(gè)bug,如果你遇到這個(gè)問(wèn)題,就需要升級(jí)JDK了。

還記得上次說(shuō)的平均晉升大小嗎,在并行GC時(shí),如果平均晉升大小大于old剩余空間,則發(fā)生full GC,那么當(dāng)小于剩余空間時(shí),也就是平均晉升小于剩余空間,但是剩余空間小于eden + 一個(gè)survivor的空間時(shí),此時(shí)就依賴于參數(shù):

-XX:-HandlePromotionFailure

啟動(dòng)該參數(shù)時(shí),上述情況成立就發(fā)生minor gc(YGC),大于則發(fā)生full gc(major gc)。

一般默認(rèn)直接分配的對(duì)象如果大于Eden的一半就會(huì)直接晉升到old區(qū)域,但是也可以通過(guò)參數(shù)來(lái)指定:

-XX:PretenureSizeThreshold=2m 我個(gè)人不建議使用這個(gè)參數(shù)

也就是當(dāng)申請(qǐng)對(duì)象大于這個(gè)值就會(huì)晉升到old區(qū)域。

傳說(shuō)中GC時(shí)間的限制,一個(gè)是通過(guò)比例限制,一個(gè)是通過(guò)最大暫停時(shí)間限制,但是GC時(shí)間能限制么,呵呵,在增量中貌似可以限制,不過(guò)不能限制住GC總體的時(shí)間,所以這個(gè)參數(shù)也不是那么關(guān)鍵。

-XX:GCTimeRatio=

-XX:MaxGCPauseMillis

-XX:GCTimeLimit

要看到真正暫停的時(shí)間就一個(gè)是看GCDetail的日志,另一個(gè)是設(shè)置參數(shù)看:

-XX:+PrintGCApplicationStoppedTime

有些人,有些人就是喜歡在代碼里面里頭寫(xiě)System.gc(),耍酷,這個(gè)不是測(cè)試程序是線上業(yè)務(wù),這樣將會(huì)導(dǎo)致N多的問(wèn)題,不多說(shuō)了,你應(yīng)該懂的,不懂的話看下書(shū)吧,而RMI是很不聽(tīng)話的一個(gè)鳥(niǎo)玩意,EJB的框架也是基于RMI寫(xiě)的,RMI為什么不聽(tīng)話呢,就是它自己在里面非要搞個(gè)System.gc(),哎,為了放置頻繁的做,頻繁的做,你就將這個(gè)命令的執(zhí)行禁用掉吧,當(dāng)然程序不用改,不然那些EJB都跑步起來(lái)了,呵呵:

-XX:+DisableExplicitGC 默認(rèn)是沒(méi)有禁用掉,寫(xiě)成+就是禁用掉的了,但是有些時(shí)候在使用allocateDirect的時(shí)候,很多時(shí)候還真需要System.gc來(lái)強(qiáng)制回收這塊資源。

內(nèi)存溢出時(shí)導(dǎo)出溢出的錯(cuò)誤信息:
-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=/home/xieyu/logs/ 這個(gè)參數(shù)指定導(dǎo)出時(shí)的路徑,不然導(dǎo)出的路徑就是虛擬機(jī)的目標(biāo)位置,不好找了,默認(rèn)的文件名是:java_pid<進(jìn)程號(hào)>.hprof,這個(gè)文件可以類似使用jmap -dump:file=....,format=b <pid>來(lái)dump類似的內(nèi)容,文件后綴都是hprof,然后下載mat工具進(jìn)行分析即可(不過(guò)內(nèi)存有多大dump文件就多大,而本地分析的時(shí)候內(nèi)存也需要那么大,所以很多時(shí)候下載到本地都無(wú)法啟動(dòng)是很正常的),后續(xù)文章有機(jī)會(huì)我們來(lái)說(shuō)明這些工具,另外jmap -dump參數(shù)也不要經(jīng)常用,會(huì)導(dǎo)致應(yīng)用掛起哦;另外此參數(shù)只會(huì)在第一次輸出OOM的時(shí)候才會(huì)進(jìn)行堆的dump操作(java heap的溢出是可以繼續(xù)運(yùn)行再運(yùn)行的程序的,至于web應(yīng)用是否服務(wù)要看應(yīng)用服務(wù)器自身如何處理,而c heap區(qū)域的溢出就根本沒(méi)有dump的機(jī)會(huì),因?yàn)橹苯泳湾礄C(jī)了,目前系統(tǒng)無(wú)法看到c heap的大小以及內(nèi)部變化,要看大小只能間接通過(guò)看JVM進(jìn)程的內(nèi)存大?。╰op或類似參數(shù)),這個(gè)大小一般會(huì)大于heap+perm的大小,多余的部分基本就可以認(rèn)為是c heap的大小了,而看內(nèi)部變化呢只有g(shù)oogle perftools可以達(dá)到這個(gè)目的),如果內(nèi)存過(guò)大這個(gè)dump操作將會(huì)非常長(zhǎng),所以hotspot如果以后想管理大內(nèi)存,這塊必須有新的辦法出來(lái)。

最后,用dump出來(lái)的文件,通過(guò)mat分析出來(lái)的結(jié)果往往有些時(shí)候難以直接確定到底哪里有問(wèn)題,可以看到的維度大概有:那個(gè)類使用的內(nèi)存最多,以及每一個(gè)線程使用的內(nèi)存,以及線程內(nèi)部每一個(gè)調(diào)用的類和方法所使用的內(nèi)存,但是很多時(shí)候無(wú)法判定到底是程序什么地方調(diào)用了這個(gè)類或者方法,因?yàn)檫@里只能看到最終消耗內(nèi)存的類,但是不知道誰(shuí)使用了它,一個(gè)辦法是掃描代碼,但是太笨重,而且如果是jar包中調(diào)用了就不好弄了,另一種方法是寫(xiě)agent,那么就需要相應(yīng)的配合了,但是有一個(gè)非常好的工具就是btrace工具(jdk 1.7貌似還不支持),可以跟蹤到某個(gè)類的某個(gè)方法被那些類中的方法調(diào)用過(guò),那這個(gè)問(wèn)題就好說(shuō)了,只要知道開(kāi)銷內(nèi)存的是哪一個(gè)類,就能知道誰(shuí)調(diào)用過(guò)它,OK,關(guān)于btrace的不是本文重點(diǎn),網(wǎng)上都有,后續(xù)文章有機(jī)會(huì)再探討,
原理:
No performance impact during runtime(無(wú)性能影響)
Dumping a –Xmx512m heap
Create a 512MB .hprof file(512M內(nèi)存就dump出512M的空間大?。?br> JVM is “dead” during dumping(死掉時(shí)dump)
Restarting JVM during this dump will cause unusable .hprof file(重啟導(dǎo)致文件不可用)

注明的NUMA架構(gòu),在JVM中開(kāi)始支持,當(dāng)然也需要CPU和OS的支持才可以,需要設(shè)置參數(shù)為:

-XX:+UseNUMA 必須在并行GC的基礎(chǔ)上才有的

老年代無(wú)法分配區(qū)域的最大等待時(shí)間為(默認(rèn)值為0,但是也不要去動(dòng)它):

-XX:GCExpandToAllocateDelayMillis

讓JVM中所有的set和get方法轉(zhuǎn)換為本地代碼:

-XX:+UseFastAccessorMethods

以時(shí)間戳輸出Heap的利用率

-XX:+PrintHeapUsageOverTime

在64bit的OS上面(其實(shí)一般達(dá)不到57位左右),由于指針會(huì)放大為8個(gè)byte,所以會(huì)導(dǎo)致空間使用增加,當(dāng)然,如果內(nèi)存夠大,就沒(méi)有問(wèn)題,但是如果升級(jí)到64bit系統(tǒng)后,只是想讓內(nèi)存達(dá)到4G或者8G,那么就完全可以通過(guò)很多指針壓縮為4byte就OK了,所以在提供以下參數(shù)(本參數(shù)于jdk 1.6u23后使用,并自動(dòng)開(kāi)啟,所以也不需要你設(shè)置,知道就OK):

-XX:+UseCompressedOops 請(qǐng)注意:這個(gè)參數(shù)默認(rèn)在64bit的環(huán)境下默認(rèn)啟動(dòng),但是如果JVM的內(nèi)存達(dá)到32G后,這個(gè)參數(shù)就會(huì)默認(rèn)為不啟動(dòng),因?yàn)?2G內(nèi)存后,壓縮就沒(méi)有多大必要了,要管理那么大的內(nèi)存指針也需要很大的寬度了。

后臺(tái)JIT編譯優(yōu)化啟動(dòng)

-XX:+BackgroundCompilation

如果你要輸出GC的日志以及時(shí)間戳,相關(guān)的參數(shù)有:

-XX:+PrintGCDetails 輸出GC的日志詳情,包含了時(shí)間戳

-XX:+PrintGCTimeStamps 輸出GC的時(shí)間戳信息,按照啟動(dòng)JVM后相對(duì)時(shí)間的每次GC的相對(duì)秒值(毫秒在小數(shù)點(diǎn)后面),也就是每次GC相對(duì)啟動(dòng)JVM啟動(dòng)了多少秒后發(fā)生了這次GC

-XX:+PrintGCDateStamps輸出GC的時(shí)間信息,會(huì)按照系統(tǒng)格式的日期輸出每次GC的時(shí)間

-XX:+PrintGCTaskTimeStamps輸出任務(wù)的時(shí)間戳信息,這個(gè)細(xì)節(jié)上比較復(fù)雜,后續(xù)有文章來(lái)探討。

-XX:-TraceClassLoading 跟蹤類的裝載

-XX:-TraceClassUnloading 跟蹤類的卸載

-XX:+PrintHeapAtGC 輸出GC后各個(gè)堆板塊的大小。

將常量信息GC信息輸出到日志文件:

-Xloggc:/home/xieyu/logs/gc.log

現(xiàn)在面對(duì)大內(nèi)存比較流行是是CMS GC(最少1.5才支持),首先明白CMS的全稱是什么,不是傳統(tǒng)意義上的內(nèi)容管理系統(tǒng)(Content Management System)哈,第一次我也沒(méi)看懂,它的全稱是:Concurrent Mark Sweep,三個(gè)單詞分別代表并發(fā)、標(biāo)記、清掃(主意這里沒(méi)有compact操作,其實(shí)CMS GC的確沒(méi)有compact操作),也就是在程序運(yùn)行的同時(shí)進(jìn)行標(biāo)記和清掃工作,至于它的原理前面有提及過(guò),只是有不同的廠商在上面做了一些特殊的優(yōu)化,比如一些廠商在標(biāo)記根節(jié)點(diǎn)的過(guò)程中,標(biāo)記完當(dāng)前的根,那么這個(gè)根下面的內(nèi)容就不會(huì)被暫?;謴?fù)運(yùn)行了,而移動(dòng)過(guò)程中,通過(guò)讀屏障來(lái)看這個(gè)內(nèi)存是不是發(fā)生移動(dòng),如果在移動(dòng)稍微停一下,移動(dòng)過(guò)去后再使用,hotspot還沒(méi)這么厲害,暫停時(shí)間還是挺長(zhǎng)的,只是相對(duì)其他的GC策略在面對(duì)大內(nèi)存來(lái)講是不錯(cuò)的選擇。

下面看一些CMS的策略(并發(fā)GC總時(shí)間會(huì)比常規(guī)的并行GC長(zhǎng),因?yàn)樗窃谶\(yùn)行時(shí)去做GC,很多資源征用都會(huì)影響其GC的效率,而總體的暫停時(shí)間會(huì)短暫很多很多,其并行線程數(shù)默認(rèn)為:(上面設(shè)置的并行線程數(shù) + 3)/ 4

付:CMS是目前Hotspot管理大內(nèi)存最好的JVM,如果是常規(guī)的JVM,最佳選擇為ParallelOldGC,如果必須要以響應(yīng)時(shí)間為準(zhǔn),則選擇CMS,不過(guò)CMS有兩個(gè)隱藏的隱患:

1、CMS GC雖然是并發(fā)且并行運(yùn)行的GC,但是初始化的時(shí)候如果采用默認(rèn)值92%(JVM 1.5的白皮書(shū)上描述為68%其實(shí)是錯(cuò)誤的,1.6是正確的),就很容易出現(xiàn)問(wèn)題,因?yàn)镃MS GC僅僅針對(duì)Old區(qū)域,Yong區(qū)域使用ParNew算法,也就是Old的CMS回收和Yong的回收可以同時(shí)進(jìn)行,也就是回收過(guò)程中Yong有可能會(huì)晉升對(duì)象Old,并且業(yè)務(wù)也可以同時(shí)運(yùn)行,所以92%基本開(kāi)始啟動(dòng)CMS GC很有可能old的內(nèi)存就不夠用了,當(dāng)內(nèi)存不夠用的時(shí)候,就啟動(dòng)Full GC,并且這個(gè)Full GC是串行的,所以如果弄的不好,CMS會(huì)比并行GC更加慢,為什么要啟用串行是因?yàn)镃MS GC、并行GC、串行GC的繼承關(guān)系決定的,簡(jiǎn)單說(shuō)就是它沒(méi)辦法去調(diào)用并行GC的代碼,細(xì)節(jié)說(shuō)后續(xù)有文章來(lái)細(xì)節(jié)說(shuō)明),建議這個(gè)值設(shè)置為70%左右吧,不過(guò)具體時(shí)間還是自己決定。

2、CMS GC另一個(gè)大的隱患,其實(shí)不看也差不多應(yīng)該清楚,看名字就知道,就是不會(huì)做Compact操作,它最惡心的地方也在這里,所以上面才說(shuō)一般的應(yīng)用都不使用它,它只有內(nèi)存垃圾非常多,多得無(wú)法分配晉升的空間的時(shí)候才會(huì)出現(xiàn)一次compact,但是這個(gè)是Full GC,也就是上面的串行,很恐怖的,所以內(nèi)存不是很大的,不要考慮使用它,而且它的算法十分復(fù)雜。

還有一些小的隱患是:和應(yīng)用一起征用CPU(不過(guò)這個(gè)不是大問(wèn)題,增加CPU即可)、整個(gè)運(yùn)行過(guò)程中時(shí)間比并行GC長(zhǎng)(這個(gè)也不是大問(wèn)題,因?yàn)槲覀兏雨P(guān)心暫停時(shí)間而不是運(yùn)行時(shí)間,因?yàn)闀和?huì)影響非常多的業(yè)務(wù))。

啟動(dòng)CMS為全局GC方法(注意這個(gè)參數(shù)也不能上面的并行GC進(jìn)行混淆,Yong默認(rèn)是并行的,上面已經(jīng)說(shuō)過(guò)

-XX:+UseConcMarkSweepGC

在并發(fā)GC下啟動(dòng)增量模式,只能在CMS GC下這個(gè)參數(shù)才有效。

-XX:+CMSIncrementalMode

啟動(dòng)自動(dòng)調(diào)節(jié)duty cycle,即在CMS GC中發(fā)生的時(shí)間比率設(shè)置,也就是說(shuō)這段時(shí)間內(nèi)最大允許發(fā)生多長(zhǎng)時(shí)間的GC工作是可以調(diào)整的。

-XX:+CMSIncrementalPacing

在上面這個(gè)參數(shù)設(shè)定后可以分別設(shè)置以下兩個(gè)參數(shù)(參數(shù)設(shè)置的比率,范圍為0-100):

-XX:CMSIncrementalDutyCycleMin=0
-XX:CMSIncrementalDutyCycle=10

增量GC上還有一個(gè)保護(hù)因子(CMSIncrementalSafetyFactor),不太常用;CMSIncrementalOffset提供增量GC連續(xù)時(shí)間比率的設(shè)置;CMSExpAvgFactor為增量并發(fā)的GC增加權(quán)重計(jì)算。

-XX:CMSIncrementalSafetyFactor=
-XX:CMSIncrementalOffset=
-XX:CMSExpAvgFactor=

是否啟動(dòng)并行CMS GC(默認(rèn)也是開(kāi)啟的)

-XX:+CMSParallelRemarkEnabled

要單獨(dú)對(duì)CMS GC設(shè)置并行線程數(shù)就設(shè)置(默認(rèn)也不需要設(shè)置):

-XX:ParallelCMSThreads

對(duì)PernGen進(jìn)行垃圾回收:

JDK 1.5在CMS GC基礎(chǔ)上需要設(shè)置參數(shù)(也就是前提是CMS GC才有):

-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled

1.6以后的版本無(wú)需設(shè)置:-XX:+CMSPermGenSweepingEnabled,注意,其實(shí)一直以來(lái)Full GC都會(huì)觸發(fā)對(duì)Perm的回收過(guò)程,CMS GC需要有一些特殊照顧,雖然VM會(huì)對(duì)這塊區(qū)域回收,但是Perm回收的條件幾乎不太可能實(shí)現(xiàn),首先需要這個(gè)類的classloader必須死掉,才可以將該classloader下所有的class干掉,也就是要么全部死掉,要么全部活著;另外,這個(gè)classloader下的class沒(méi)有任何object在使用,這個(gè)也太苛刻了吧,因?yàn)槌R?guī)的對(duì)象申請(qǐng)都是通過(guò)系統(tǒng)默認(rèn)的,應(yīng)用服務(wù)器也有自己默認(rèn)的classloader,要讓它死掉可能性不大,如果這都死掉了,系統(tǒng)也應(yīng)該快掛了。

CMS GC因?yàn)槭窃诔绦蜻\(yùn)行時(shí)進(jìn)行GC,不會(huì)暫停,所以不能等到不夠用的時(shí)候才去開(kāi)啟GC,官方說(shuō)法是他們的默認(rèn)值是68%,但是可惜的是文檔寫(xiě)錯(cuò)了,經(jīng)過(guò)很多測(cè)試和源碼驗(yàn)證這個(gè)參數(shù)應(yīng)該是在92%的時(shí)候被啟動(dòng),雖然還有8%的空間,但是還是很可憐了,當(dāng)CMS發(fā)現(xiàn)內(nèi)存實(shí)在不夠的時(shí)候又回到常規(guī)的并行GC,所以很多人在沒(méi)有設(shè)置這個(gè)參數(shù)的時(shí)候發(fā)現(xiàn)CMS GC并沒(méi)有神馬優(yōu)勢(shì)嘛,和并行GC一個(gè)鳥(niǎo)樣子甚至于更加慢,所以這個(gè)時(shí)候需要設(shè)置參數(shù)(這個(gè)參數(shù)在上面已經(jīng)說(shuō)過(guò),啟動(dòng)CMS一定要設(shè)置這個(gè)參數(shù)):

-XX:CMSInitiatingOccupancyFraction=70

這樣保證Old的內(nèi)存在使用到70%的時(shí)候,就開(kāi)始啟動(dòng)CMS了;如果你真的想看看默認(rèn)值,那么就使用參數(shù):-XX:+PrintCMSInitiationStatistics 這個(gè)變量只有JDK 1.6可以使用 1.5不可以,查看實(shí)際值-XX:+PrintCMSStatistics;另外,還可以設(shè)置參數(shù)-XX:CMSInitiatingPermOccupancyFraction來(lái)設(shè)置Perm空間達(dá)到多少時(shí)啟動(dòng)CMS GC,不過(guò)意義不大。

JDK 1.6以后有些時(shí)候啟動(dòng)CMS GC是根據(jù)計(jì)算代價(jià)進(jìn)行啟動(dòng),也就是不一定按照你指定的參數(shù)來(lái)設(shè)置的,如果你不想讓它按照所謂的成本來(lái)計(jì)算GC的話,那么你就使用一個(gè)參數(shù):-XX:+UseCMSInitiatingOccupancyOnly,默認(rèn)是false,它就只會(huì)按照你設(shè)置的比率來(lái)啟動(dòng)CMS GC了。如果你的程序中有System.gc以及設(shè)置了ExplicitGCInvokesConcurrent在jdk 1.6中,這種情況使用NIO是有可能產(chǎn)生問(wèn)題的。

啟動(dòng)CMS GC的compation操作,也就是發(fā)生多少次后做一次全局的compaction:

-XX:+UseCMSCompactAtFullCollection

-XX:CMSFullGCsBeforeCompaction:發(fā)生多少次CMS Full GC,這個(gè)參數(shù)最好不要設(shè)置,因?yàn)橐鯿ompaction的話,也就是真正的Full GC是串行的,非常慢,讓它自己去決定什么時(shí)候需要做compaction。

-XX:CMSMaxAbortablePrecleanTime=5000 設(shè)置preclean步驟的超時(shí)時(shí)間,單位為毫秒,preclean為cms gc其中一個(gè)步驟,關(guān)于cms gc步驟比較多,本文就不細(xì)節(jié)探討了。

并行GC在mark階段,可能會(huì)同時(shí)發(fā)生minor GC,old區(qū)域也可能發(fā)生改變,于是并發(fā)GC會(huì)對(duì)發(fā)生了改變的內(nèi)容進(jìn)行remark操作,這個(gè)觸發(fā)的條件是:

-XX:CMSScheduleRemarkEdenSizeThreshold

-XX:CMSScheduleRemarkEdenPenetration

即Eden區(qū)域多大的時(shí)候開(kāi)始觸發(fā),和eden使用量超過(guò)百分比多少的時(shí)候觸發(fā),前者默認(rèn)是2M,后者默認(rèn)是50%。

但是如果長(zhǎng)期不做remark導(dǎo)致old做不了,可以設(shè)置超時(shí),這個(gè)超時(shí)默認(rèn)是5秒,可以通過(guò)參數(shù):

-XX:CMSMaxAbortablePrecleanTime

-XX:+ExplicitGCInvokesConcurrent 在顯示發(fā)生GC的時(shí)候,允許進(jìn)行并行GC。

-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses 幾乎和上面一樣,只不過(guò)多一個(gè)對(duì)Perm區(qū)域的回收而已。

補(bǔ)充:

其實(shí)JVM還有很多的版本,很多的廠商,與其優(yōu)化的原則,隨便舉兩個(gè)例子hotspot在GC中做的一些優(yōu)化(這里不說(shuō)代碼的編譯時(shí)優(yōu)化或運(yùn)行時(shí)優(yōu)化):

Eden申請(qǐng)的空間對(duì)象由Old區(qū)域的某個(gè)對(duì)象的一個(gè)屬性指向(也就是Old區(qū)域的這個(gè)空間不回收,Eden這塊就沒(méi)有必要考慮回收),所以Hotspot在CPU寫(xiě)上面,做了一個(gè)屏障,當(dāng)發(fā)生賦值語(yǔ)句的時(shí)候(對(duì)內(nèi)存來(lái)講賦值就是一種寫(xiě)操作),如果發(fā)現(xiàn)是一個(gè)新的對(duì)象由Old指向Eden,那么就會(huì)將這個(gè)對(duì)象記錄在一個(gè)卡片機(jī)里面,這個(gè)卡片機(jī)是有很多512字節(jié)的卡片組成,當(dāng)在YGC過(guò)程中,就基本不會(huì)去移動(dòng)或者管理這塊對(duì)象(付:這種卡片機(jī)會(huì)在CMS GC的算法中使用,不過(guò)和這個(gè)卡片不是放在同一個(gè)地方的,也是CMS GC的關(guān)鍵,對(duì)于CMS GC的算法細(xì)節(jié)描述,后續(xù)文章我們單獨(dú)說(shuō)明)。

Old區(qū)域?qū)τ谝恍┍容^大的對(duì)象,JVM就不會(huì)去管理個(gè)對(duì)象,也就是compact過(guò)程中不會(huì)去移動(dòng)這塊對(duì)象的區(qū)域等等吧。

以上大部分參數(shù)為hotspot的自帶關(guān)于性能的參數(shù),參考版本為JDK 1.5和1.6的版本,很多為個(gè)人經(jīng)驗(yàn)說(shuō)明,不足以說(shuō)明所有問(wèn)題,如果有問(wèn)題,歡迎探討;另外,JDK的參數(shù)是不是就只有這些呢,肯定并不是,我知道的也不止這些,但是有些覺(jué)得沒(méi)必要說(shuō)出來(lái)的參數(shù)和一些數(shù)學(xué)運(yùn)算的參數(shù)我就不想給出來(lái)了,比如像禁用掉GC的參數(shù)有神馬意義,我們的服務(wù)器要是把這個(gè)禁用掉干個(gè)屁啊,呵呵,做測(cè)試還可以用這玩玩,讓它不做GC直接溢出;還有一些什么計(jì)算因子啥的,還有很多復(fù)雜的數(shù)學(xué)運(yùn)算規(guī)則,要是把這個(gè)配置明白了,就太那個(gè)了,而且一般情況下也沒(méi)那個(gè)必要,JDK到現(xiàn)在的配置參數(shù)多達(dá)上500個(gè)以上,要知道完的話慢慢看吧,不過(guò)意義不大,而且要知道默認(rèn)值最靠譜的是看源碼而不是看文檔,官方文檔也只能保證絕大部是正確的,不能保證所有的是正確的。

本文最后追加在jdk 1.6u 24后通過(guò)上面說(shuō)明的-XX:+PrintFlagsFinal輸出的參數(shù)以及默認(rèn)值(還是那句話,在不同的平臺(tái)上是不一樣的),輸出的參數(shù)如下,可以看看JVM的參數(shù)是相當(dāng)?shù)亩啵瑓?shù)如此之多,你只需要掌握關(guān)鍵即可,參數(shù)還有很多有沖突的,不要糾結(jié)于每一個(gè)參數(shù)的細(xì)節(jié):

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 原文閱讀 前言 這段時(shí)間懈怠了,罪過(guò)! 最近看到有同事也開(kāi)始用上了微信公眾號(hào)寫(xiě)博客了,挺好的~給他們點(diǎn)贊,這博客我...
    碼農(nóng)戲碼閱讀 6,144評(píng)論 2 31
  • Java 虛擬機(jī)有自己完善的硬件架構(gòu), 如處理器、堆棧、寄存器等,還具有相應(yīng)的指令系統(tǒng)。JVM 屏蔽了與具體操作系...
    尹小凱閱讀 1,747評(píng)論 0 10
  • 1 CPU和內(nèi)存的交互 了解jvm內(nèi)存模型前,了解下cpu和計(jì)算機(jī)內(nèi)存的交互情況?!疽?yàn)镴ava虛擬機(jī)內(nèi)存模型定義...
    Garwer閱讀 374,279評(píng)論 54 551
  • 轉(zhuǎn)載blog.csdn.net/ning109314/article/details/10411495/ JVM工...
    forever_smile閱讀 5,504評(píng)論 1 56
  • 內(nèi)存溢出和內(nèi)存泄漏的區(qū)別 內(nèi)存溢出:out of memory,是指程序在申請(qǐng)內(nèi)存時(shí),沒(méi)有足夠的內(nèi)存空間供其使用,...
    Aimerwhy閱讀 799評(píng)論 0 1

友情鏈接更多精彩內(nèi)容