對於JVM的內存寫過的文章已經有點多了,並且有點爛了,不過說那麼多大多數在解決OOM的狀況,於此,本文就只闡述這個內容,攜帶一些分析和理解和部分擴展內容,也就是JVM宕機中的一些問題,OK,下面說下OOM的常見狀況:java
第一類內存溢出,也是你們認爲最多,第一反應認爲是的內存溢出,就是堆棧溢出:web
那什麼樣的狀況就是堆棧溢出呢?當你看到下面的關鍵字的時候它就是堆棧溢出了:算法
java.lang.OutOfMemoryError: ......java heap space.....windows
也就是當你看到heap相關的時候就確定是堆棧溢出了,此時若是代碼沒有問題的狀況下,適當調整-Xmx和-Xms是能夠避免的,不過必定是代碼沒有問題的前提,爲何會溢出呢,要麼代碼有問題,要麼訪問量太多而且每一個訪問的時間太長或者數據太多,致使數據釋放不掉,由於垃圾回收器是要找到那些是垃圾才能回收,這裏它不會認爲這些東西是垃圾,天然不會去回收了;主意這個溢出以前,可能系統會提早先報錯關鍵字爲:服務器
java.lang.OutOfMemoryError:GC over head limit exceeded多線程
這種狀況是當系統處於高頻的GC狀態,並且回收的效果依然不佳的狀況,就會開始報這個錯誤,這種狀況通常是產生了不少不能夠被釋放的對象,有多是引用使用不當致使,或申請大對象致使,可是java heap space的內存溢出有可能提早不會報這個錯誤,也就是可能內存就直接不夠致使,而不是高頻GC.架構
第二類內存溢出,PermGen的溢出,或者PermGen 滿了的提示,你會看到這樣的關鍵字:併發
關鍵信息爲:框架
java.lang.OutOfMemoryError: PermGen spacejvm
緣由:系統的代碼很是多或引用的第三方包很是多、或代碼中使用了大量的常量、或經過intern注入常量、或者經過動態代碼加載等方法,致使常量池的膨脹,雖然JDK 1.5之後能夠經過設置對永久帶進行回收,可是咱們但願的是這個地方是不作GC的,它夠用就行,因此通常狀況下今年少作相似的操做,因此在面對這種狀況經常使用的手段是:增長-XX:PermSize和-XX:MaxPermSize的大小。
第三類內存溢出:在使用ByteBuffer中的allocateDirect()的時候會用到,不少javaNIO的框架中被封裝爲其餘的方法
溢出關鍵字:
java.lang.OutOfMemoryError: Direct buffer memory
若是你在直接或間接使用了ByteBuffer中的allocateDirect方法的時候,而不作clear的時候就會出現相似的問題,常規的引用程序IO輸出存在一個內核態與用戶態的轉換過程,也就是對應直接內存與非直接內存,若是常規的應用程序你要將一個文件的內容輸出到客戶端須要經過OS的直接內存轉換拷貝到程序的非直接內存(也就是heap中),而後再輸出到直接內存由操做系統發送出去,而直接內存就是由OS和應用程序共同管理的,而非直接內存能夠直接由應用程序本身控制的內存,jvm垃圾回收不會回收掉直接內存這部分的內存,因此要注意了哦。
若是常常有相似的操做,能夠考慮設置參數:-XX:MaxDirectMemorySize
第四類內存溢出錯誤:
溢出關鍵字:
java.lang.StackOverflowError
這個參數直接說明一個內容,就是-Xss過小了,咱們申請不少局部調用的棧針等內容是存放在用戶當前所持有的線程中的,線程在jdk 1.4之前默認是256K,1.5之後是1M,若是報這個錯,只能說明-Xss設置得過小,固然有些廠商的JVM不是這個參數,本文僅僅針對Hotspot VM而已;不過在有必要的狀況下能夠對系統作一些優化,使得-Xss的值是可用的。
第五類內存溢出錯誤:
溢出關鍵字:
java.lang.OutOfMemoryError: unable to create new native thread
上面第四種溢出錯誤,已經說明了線程的內存空間,其實線程基本只佔用heap之外的內存區域,也就是這個錯誤說明除了heap之外的區域,沒法爲線程分配一塊內存區域了,這個要麼是內存自己就不夠,要麼heap的空間設置得太大了,致使了剩餘的內存已經很少了,而因爲線程自己要佔用內存,因此就不夠用了,說明了緣由,如何去修改,不用我多說,你懂的。
第六類內存溢出:
溢出關鍵字
java.lang.OutOfMemoryError: request {} byte for {}out of swap
這類錯誤通常是因爲地址空間不夠而致使。
六大類常見溢出已經說明JVM中99%的溢出狀況,要逃出這些溢出狀況很是困難,除非一些很怪異的故障問題會發生,好比因爲物理內存的硬件問題,致使了code cache的錯誤(在由byte code轉換爲native code的過程當中出現,可是機率極低),這種狀況內存 會被直接crash掉,相似還有swap的頻繁交互在部分系統中會致使系統直接被crash掉,OS地址空間不夠的話,系統根本沒法啓動,呵呵;JNI的濫用也會致使一些本地內存沒法釋放的問題,因此儘可能避開JNI;socket鏈接數據打開過多的socket也會報相似:IOException: Too many open files等錯誤信息。
JNI就不用多說了,儘可能少用,除非你的代碼太牛B了,我無話可說,呵呵,這種內存若是沒有在被調用的語言內部將內存釋放掉(如C語言),那麼在進程結束前這些內存永遠釋放不掉,解決辦法只有一個就是將進程kill掉。
另外GC自己是須要內存空間的,由於在運算和中間數據轉換過程當中都須要有內存,因此你要保證GC的時候有足夠的內存哦,若是沒有的話GC的過程將會很是的緩慢。
順便這裏就說起一些新的CMS GC的內容和策略(有點亂,每次寫都很亂,可是能看多少看多少吧):
首先我再寫一次一前博客中的已經寫過的內容,就是不少參數沒啥建議值,建議值是本身在現場根據實際狀況科學計算和測試獲得的綜合效果,建議值沒有絕對好的,並且默認值不少也是有問題的,由於不一樣的版本和廠商都有很大的區別,默認值沒有永久都是同樣的,就像-Xss參數的變化同樣,要看到你當前的java程序heap的大體狀況能夠這樣看看(如下參數是隨便設置的,並非什麼默認值):
$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是須要拿到管理員權限,若是你的系統權限很大那麼就不須要了,最後的grep java那個內容若是不對,能夠直接經過jps或者ps命令將和java相關的進程號直接寫進去,如:java -map 4280,這個參數其實徹底能夠經過jstat工具來替代,並且看到的效果更加好,這個參數在線上應用中,儘可能少用(尤爲是高併發的應用中),可能會觸發JVM的bug,致使應用掛起;在jvm 1.6u14後能夠編寫任意一段程序,而後在運行程序的時候,增長參數爲:-XX:+PrintFlagsFinal來輸出當前JVM中運行時的參數值,或者經過jinfo來查看,jinfo是很是強大的工具,能夠對部分參數進行動態修改,固然內存相關的東西是不能修改的,只能增長一些不是很相關的參數,有關JVM的工具使用,後續文章中若是有機會咱們再來探討,不是本文的重點;補充:關於參數的默認值對不一樣的JVM版本、不一樣的廠商、運行於不一樣的環境(通常和位數有關係)默認值會有區別。
OK,再說下反覆的一句,沒有必要的話就不要亂設置參數,參數不是拿來玩的,默認的參數對於這門JDK都是有好處的,關鍵是否適合你的應用場景,通常來說你常規的只須要設置如下幾個參數就能夠了:
-server 表示爲服務器端,會提供不少服務器端默認的配置,如並行回收,而服務器上通常這個參數都是默認的,因此都是能夠省掉,與之對應的還有一個-client參數,通常在64位機器上,JVM是默認啓動-server參數,也就是默認啓動並行GC的,可是是ParallelGC而不是ParallelOldGC,二者算法不一樣(後面會簡單說明下),而比較特殊的是windows 32位上默認是-client,這兩個的區別不只僅是默認的參數不同,在jdk包下的jre包下通常會包含client和server包,下面分別對應啓動的動態連接庫,而真正看到的java、javac等相關命令指示一個啓動導向,它只是根據命令找到對應的JVM並傳入jvm中進行啓動,也就是看到的java.exe這些文件並非jvm;說了這麼多,最終總結一下就是,-server和-client就是徹底不一樣的兩套VM,一個用於桌面應用,一個用於服務器的。
-Xmx 爲Heap區域的最大值
-Xms 爲Heap區域的初始值,線上環境須要與-Xmx設置爲一致,不然capacity的值會來回飄動,飄得你心曠神怡,你懂的。
-Xss(或-ss) 這個其實也是能夠默認的,若是你真的以爲有設置的必要,你就改下吧,1.5之後是1M的默認大小(指一個線程的native空間),若是代碼很少,能夠設置小點來讓系統能夠接受更大的內存。注意,還有一個參數是-XX:ThreadStackSize,這兩個參數在設置的過程當中若是都設置是有衝突的,通常按照JVM常理來講,誰設置在後面,就以誰爲主,可是最後發現若是是在1.6以上的版本,-Xss設置在後面的確都是以-Xss爲主,可是要是-XX:ThreadStackSize設置在後面,主線程仍是爲-Xss爲主,而其它線程以-XX:ThreadStackSize爲主,主線程作了一個特殊斷定處理;單獨設置都是以自己爲主,-Xss不設置也不會採用其默認值,除非兩個都不設置會採用-Xss的默認值。另外這個參數針對於hotspot的vm,在IBM的jvm中,還有一個參數爲-Xoss,主要緣由是IBM在對棧的處理上有操做數棧和方法棧等各類不一樣的棧種類,而hotspot無論是什麼棧都放在一個私有的線程內部的,不區分是什麼棧,因此只須要設置一個參數,而IBM的J9不是這樣的;有關棧上的細節,後續咱們有機會專門寫文章來講明。
-XX:PermSize與-XX:MaxPermSize兩個包含了class的裝載的位置,或者說是方法區(但不是本地方法區),在Hotspot默認狀況下爲64M,主意全世界的JVM只有hostpot的VM纔有Perm的區域,或者說只有hotspot纔有對用戶能夠設置的這塊區域,其餘的JVM都沒有,其實並非沒有這塊區域,而是這塊區域沒有讓用戶來設置,其實這塊區域自己也不該該讓用戶來設置,咱們也沒有一個明確的說法這塊空間必需要設置多大,都是拍腦殼設置一個數字,若是發佈到線上看下若是用得比較多,就再多點,若是用的少,就減小點,而這塊區域和性能關鍵沒有多大關係,只要能裝下就OK,而且時不時會由於Perm不夠而致使Full GC,因此交給開發者來調節這個參數不知道是怎麼想的;因此Oracle將在新一代JVM中將這個區域完全刪掉,也就是對用戶透明,G1的若是真正穩定起來,之後JVM的啓動參數將會很是簡單,並且理論上管理再大的內存也是沒有問題的,其實G1(garbage first,一種基於region的垃圾收集回收器)已經在hotspot中開始有所試用,不過目前效果很差,還不如CMS呢,因此只是試用,G1已經做爲ORACLE對JVM研發的最高重點,CMS自如今最高版本後也再也不有新功能(能夠修改bug),該項目已經進行5年,還沒有發佈正式版,CMS是四五年前發佈的正式版,可是是最近一兩年纔開始穩定,而G1的複雜性將會遠遠超越CMS,因此要真正使用上G1還有待考察,全世界目前只有IBM J9真正實現了G1論文中提到的思想(論文於05年左右發表),IBM已經將J9應用於websphere中,可是並不表明這是全世界最好的jvm,全世界最好的jvm是Azul(無停頓垃圾回收算法和一個零開銷的診斷/監控工具),幾乎能夠說這個jvm是沒有暫停的,在全世界不少頂尖級的公司使用,不過價格很是貴,不能直接使用,目前這個jvm的主導者在研究JRockit,而目前hotspot和JRockit都是Oracle的,因此他們可能會合並,因此咱們應該對JVM的性能充滿信心。
也就是說你經常使用的狀況下只須要設置4個參數就OK了,除非你的應用有些特殊,不然不要亂改,那麼來看看一些其餘狀況的參數吧:
先來看個不大經常使用的,就是你們都知道JVM新的對象應該說幾乎百分百的在Eden裏面,除非Eden真的裝不下,咱們不考慮這種變態的問題,由於線上環境Eden區域都是不小的,來下降GC的次數以及全局 GC的機率;而JVM習慣將內存按照較爲連續的位置進行分配,這樣使得有足夠的內存能夠被分配,減小碎片,那麼對於內存最後一個位置必然就有大量的徵用問題,JVM在高一點的版本里面提出了爲每一個線程分配一些私有的區域來作來解決這個問題,而1.5後的版本還能夠動態管理這些區域,那麼如何本身設置和查看這些區域呢,看下英文全稱爲:Thread Local Allocation Buffer,簡稱就是:TLAB,即內存本地的持有的buffer,設置參數有:
-XX:+UseTLAB 啓用這種機制的意思
-XX:TLABSize=<size in kb> 設置大小,也就是本地線程中的私有區域大小(只有這個區域放不下才會到Eden中去申請)。
-XX:+ResizeTLAB 是否啓動動態修改
這幾個參數在多CPU下很是有用。
-XX:+PrintTLAB 能夠輸出TLAB的內容。
下面再閒扯些其它的參數:
若是你須要對Yong區域進行並行回收應該如何修改呢?在jdk1.5之後可使用參數:
-XX:+UseParNewGC
注意: 與它衝突的參數是:-XX:+UseParallelOldGC和-XX:+UseSerialGC,若是須要用這個參數,又想讓整個區域是並行回收的,那麼就使用-XX:+UseConcMarkSweepGC參數來配合,其實這個參數在使用了CMS後,默認就會啓動該參數,也就是這個參數在CMS GC下是無需設置的,後面會說起到這些參數。
默認服務器上的對Full並行GC策略爲(這個時候Yong空間回收的時候啓動PSYong算法,也是並行回收的):
-XX:+UseParallelGC
另外,在jdk1.5後出現一個新的參數以下,這個對Yong的回收算法和上面同樣,對Old區域會有所區別,上面對Old回收的過程當中會作一個全局的Compact,也就是全局的壓縮操做,而下面的算法是局部壓縮,爲何要局部壓縮呢?是由於JVM發現每次壓縮後再邏輯上數據都在Old區域的左邊位置,申請的時候從左向右申請,那麼生命力越長的對象就通常是靠左的,因此它認爲左邊的對象就是生命力很強,並且較爲密集的,因此它針對這種狀況進行部分密集,可是這兩種算法mark階段都是會暫停的,並且存活的對象越多活着的越多;而ParallelOldGC會進行部分壓縮算法(主意一點,最原始的copy算法是不須要通過mark階段,由於只須要找到一個或活着的就只須要作拷貝就能夠,而Yong區域借用了Copy算法,只是惟一的區別就是傳統的copy算法是採用兩個相同大小的內存來拷貝,浪費空間爲50%,因此分代的目標就是想要實現不少優點所在,認爲新生代85%以上的對象都應該是死掉的,因此S0和S1通常並非很大),該算法爲jdk 1.5之後對於絕大部分應用的最佳選擇。
-XX:+UseParallelOldGC
-XX:ParallelGCThread=12:並行回收的線程數,最好根據實際狀況而定,由於線程多每每存在徵用調度和上下文切換的開銷;並且也並不是CPU越多線程數也能夠設置越大,通常設置爲12就再增長用處也不大,主要是算法自己內部的徵用會致使其線程的極限就是這樣。
設置Yong區域大小:
-Xmn Yong區域的初始值和最大值同樣大
-XX:NewSize和-XX:MaxNewSize若是設置覺得同樣大就是和-Xmn,在JRockit中會動態變化這些參數,根據實際狀況有可能會變化出兩個Yong區域,或者沒有Yong區域,有些時候會生出來一個半長命對象區域;這裏除了這幾個參數外,還有一個參數是NewRatio是設置Old/Yong的倍數的,這幾個參數都是有衝突的,服務器端建議是設置-Xmn就能夠了,若是幾個參數所有都有設置,-Xmn和-XX:NewSize與-XX:MaxNewSize將是誰設置在後面,以誰的爲準,而-XX:NewSize -XX:MaxNewSize與-XX:NewRatio時,那麼參數設置的結果可能會如下這樣的(jdk 1.4.1後):
min(MaxNewSize,max(NewSize, heap/(NewRatio+1)))
-XX:NewRatio爲Old區域爲Yong的多少倍,間接設置Yong的大小,1.6中若是使用此參數,則默認會在適當時候被動態調整,具體請看下面參數UseAdaptiveSizepollcy 的說明。
三個參數不要同時設置,由於都是設置Yong的大小的。
-XX:SurvivorRatio:該參數爲Eden與兩個求助空間之一的比例,注意Yong的大小等價於Eden + S0 + S1,S0和S1的大小是等價的,這個參數爲Eden與其中一個S區域的大小比例,如參數爲8,那麼Eden就佔用Yong的80%,而S0和S1分別佔用10%。
之前的老版本有一個參數爲:-XX:InitialSurivivorRatio,若是不作任何設置,就會以這個參數爲準,這個參數的默認值就是8,不過這個參數並非Eden/Survivor的大小,而是Yong/Survivor,因此因此默認值8,表明每個S區域的空間大小爲Yong區域的12.5%而不是10%。另外順便說起一下,每次你們看到GC日誌的時候,GC日誌中的每一個區域的最大值,其中Yong的空間最大值,始終比設置的Yong空間的大小要小一點,大概是小12.5%左右,那是由於每次可用空間爲Eden加上一個Survivor區域的大小,而不是整個Yong的大小,由於可用空間每次最可能是這樣大,兩個Survivor區域始終有一塊是空的,因此不會加上兩個來計算。
-XX:MaxTenuringThreshold=15:在正常狀況下,新申請的對象在Yong區域發生多少次GC後就會被移動到Old(非正常就是S0或S1放不下或者不太可能出現的Eden都放不下的對象),這個參數通常不會超過16(由於計數器從0開始計數,因此設置爲15的時候至關於生命週期爲16)。
要查看如今的這個值的具體狀況,可使用參數:-XX:+PrintTenuringDistribution
經過上面的jmap應該能夠看出個人機器上的MinHeapFreeRatio和MaxHeapFreeRatio分別爲40個70,也就是你們常常說的在GC後剩餘空間小於40%時capacity開始增大,而大於70%時減少,因爲咱們不但願讓它移動,因此這兩個參數幾乎沒有意義,若是你須要設置就設置參數爲:
-XX:MinHeapFreeRatio=40
-XX:MaxHeapFreeRatio=70
JDK 1.6後有一個動態調節板塊的,固然若是你的每個板塊都是設置固定值,這個參數也沒有用,不過若是是非固定的,建議仍是不要動態調整,默認是開啓的,建議將其關掉,參數爲:
-XX:+UseAdaptiveSizepollcy 建議使用-XX:-UseAdaptiveSizepollcy關掉,爲何當你的參數設置了NewRatio、Survivor、MaxTenuringThreshold這幾個參數若是在啓動了動態更新狀況下,是無效的,固然若是你設置-Xmn是有效的,可是若是設置的比例的話,初始化可能會按照你的參數去運行,不過運行過程當中會經過必定的算法動態修改,監控中你可能會發現這些參數會發生改變,甚至於S0和S1的大小不同。
若是啓動了這個參數,又想要跟蹤變化,那麼就使用參數:-XX:+PrintAdaptiveSizePolicy
上面已經提到,javaNIO中經過Direct內存來提升性能,這個區域的大小默認是64M,在適當的場景能夠設置大一些。
-XX:MaxDirectMemorySize
一個不太經常使用的參數:
-XX:+ScavengeBeforeFullGC 默認是開啓狀態,在full GC前先進行minor GC。
對於java堆中若是要設置大頁內存,能夠經過設置參數:
付:此參數必須在操做系統的內核支持的基礎上,須要在OS級別作操做爲:
echo 1024 > /proc/sys/vm/nr_hugepages
echo 2147483647 > /proc/sys/kernel/shmmax
-XX:+UseLargePages
-XX:LargePageSizeInBytes
此時整個JVM都將在這塊內存中,不然所有不在這塊內存中。
javaIO的臨時目錄設置
-Djava.io.tmpdir
jstack會去尋找/tmp/hsperfdata_admin下去尋找與進程號相同的文件,32位機器上是沒有問題的,64爲機器的是有BUG的,在jdk 1.6u23版本中已經修復了這個bug,若是你遇到這個問題,就須要升級JDK了。
還記得上次說的平均晉升大小嗎,在並行GC時,若是平均晉升大小大於old剩餘空間,則發生full GC,那麼當小於剩餘空間時,也就是平均晉升小於剩餘空間,可是剩餘空間小於eden + 一個survivor的空間時,此時就依賴於參數:
-XX:-HandlePromotionFailure
啓動該參數時,上述狀況成立就發生minor gc(YGC),大於則發生full gc(major gc)。
通常默認直接分配的對象若是大於Eden的一半就會直接晉升到old區域,可是也能夠經過參數來指定:
-XX:PretenureSizeThreshold=2m 我我的不建議使用這個參數
也就是當申請對象大於這個值就會晉升到old區域。
傳說中GC時間的限制,一個是經過比例限制,一個是經過最大暫停時間限制,可是GC時間能限制麼,呵呵,在增量中貌似能夠限制,不過不能限制住GC整體的時間,因此這個參數也不是那麼關鍵。
-XX:GCTimeRatio=
-XX:MaxGCPauseMillis
-XX:GCTimeLimit
要看到真正暫停的時間就一個是看GCDetail的日誌,另外一個是設置參數看:
-XX:+PrintGCApplicationStoppedTime
有些人,有些人就是喜歡在代碼裏面裏頭寫System.gc(),耍酷,這個不是測試程序是線上業務,這樣將會致使N多的問題,很少說了,你應該懂的,不懂的話看下書吧,而RMI是很不聽話的一個鳥玩意,EJB的框架也是基於RMI寫的,RMI爲何不聽話呢,就是它本身在裏面非要搞個System.gc(),哎,爲了放置頻繁的作,頻繁的作,你就將這個命令的執行禁用掉吧,固然程序不用改,否則那些EJB都跑步起來了,呵呵:
-XX:+DisableExplicitGC 默認是沒有禁用掉,寫成+就是禁用掉的了,可是有些時候在使用allocateDirect的時候,不少時候還真須要System.gc來強制回收這塊資源。
內存溢出時導出溢出的錯誤信息:
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/home/xieyu/logs/ 這個參數指定導出時的路徑,否則導出的路徑就是虛擬機的目標位置,很差找了,默認的文件名是:java_pid<進程號>.hprof,這個文件能夠相似使用jmap -dump:file=....,format=b <pid>來dump相似的內容,文件後綴都是hprof,而後下載mat工具進行分析便可(不過內存有多大dump文件就多大,而本地分析的時候內存也須要那麼大,因此不少時候下載到本地都沒法啓動是很正常的),後續文章有機會咱們來講明這些工具,另外jmap -dump參數也不要常常用,會致使應用掛起哦;另外此參數只會在第一次輸出OOM的時候纔會進行堆的dump操做(java heap的溢出是能夠繼續運行再運行的程序的,至於web應用是否服務要看應用服務器自身如何處理,而c heap區域的溢出就根本沒有dump的機會,由於直接就宕機了,目前系統沒法看到c heap的大小以及內部變化,要看大小隻能間接經過看JVM進程的內存大小(top或相似參數),這個大小通常會大於heap+perm的大小,多餘的部分基本就能夠認爲是c heap的大小了,而看內部變化呢只有google perftools能夠達到這個目的),若是內存過大這個dump操做將會很是長,因此hotspot若是之後想管理大內存,這塊必須有新的辦法出來。
最後,用dump出來的文件,經過mat分析出來的結果每每有些時候難以直接肯定到底哪裏有問題,能夠看到的維度大概有:那個類使用的內存最多,以及每個線程使用的內存,以及線程內部每個調用的類和方法所使用的內存,可是不少時候沒法斷定究竟是程序什麼地方調用了這個類或者方法,由於這裏只能看到最終消耗內存的類,可是不知道誰使用了它,一個辦法是掃描代碼,可是太笨重,並且若是是jar包中調用了就很差弄了,另外一種方法是寫agent,那麼就須要相應的配合了,可是有一個很是好的工具就是btrace工具(jdk 1.7貌似還不支持),能夠跟蹤到某個類的某個方法被那些類中的方法調用過,那這個問題就好說了,只要知道開銷內存的是哪個類,就能知道誰調用過它,OK,關於btrace的不是本文重點,網上都有,後續文章有機會再探討,
原理:
No performance impact during runtime(無性能影響)
Dumping a –Xmx512m heap
Create a 512MB .hprof file(512M內存就dump出512M的空間大小)
JVM is 「dead」 during dumping(死掉時dump)
Restarting JVM during this dump will cause unusable .hprof file(重啓致使文件不可用)
註明的NUMA架構,在JVM中開始支持,固然也須要CPU和OS的支持才能夠,須要設置參數爲:
-XX:+UseNUMA 必須在並行GC的基礎上纔有的
老年代沒法分配區域的最大等待時間爲(默認值爲0,可是也不要去動它):
-XX:GCExpandToAllocateDelayMillis
讓JVM中全部的set和get方法轉換爲本地代碼:
-XX:+UseFastAccessorMethods
以時間戳輸出Heap的利用率
-XX:+PrintHeapUsageOverTime
在64bit的OS上面(其實通常達不到57位左右),因爲指針會放大爲8個byte,因此會致使空間使用增長,固然,若是內存夠大,就沒有問題,可是若是升級到64bit系統後,只是想讓內存達到4G或者8G,那麼就徹底能夠經過不少指針壓縮爲4byte就OK了,因此在提供如下參數(本參數於jdk 1.6u23後使用,並自動開啓,因此也不須要你設置,知道就OK):
-XX:+UseCompressedOops 請注意:這個參數默認在64bit的環境下默認啓動,可是若是JVM的內存達到32G後,這個參數就會默認爲不啓動,由於32G內存後,壓縮就沒有多大必要了,要管理那麼大的內存指針也須要很大的寬度了。
後臺JIT編譯優化啓動
-XX:+BackgroundCompilation
若是你要輸出GC的日誌以及時間戳,相關的參數有:
-XX:+PrintGCDetails 輸出GC的日誌詳情,包含了時間戳
-XX:+PrintGCTimeStamps 輸出GC的時間戳信息,按照啓動JVM後相對時間的每次GC的相對秒值(毫秒在小數點後面),也就是每次GC相對啓動JVM啓動了多少秒後發生了此次GC
-XX:+PrintGCDateStamps輸出GC的時間信息,會按照系統格式的日期輸出每次GC的時間
-XX:+PrintGCTaskTimeStamps輸出任務的時間戳信息,這個細節上比較複雜,後續有文章來探討。
-XX:-TraceClassLoading 跟蹤類的裝載
-XX:-TraceClassUnloading 跟蹤類的卸載
-XX:+PrintHeapAtGC 輸出GC後各個堆板塊的大小。
將常量信息GC信息輸出到日誌文件:
-Xloggc:/home/xieyu/logs/gc.log
如今面對大內存比較流行是是CMS GC(最少1.5才支持),首先明白CMS的全稱是什麼,不是傳統意義上的內容管理系統(Content Management System)哈,第一次我也沒看懂,它的全稱是:Concurrent Mark Sweep,三個單詞分別表明併發、標記、清掃(主意這裏沒有compact操做,其實CMS GC的確沒有compact操做),也就是在程序運行的同時進行標記和清掃工做,至於它的原理前面有說起過,只是有不一樣的廠商在上面作了一些特殊的優化,好比一些廠商在標記根節點的過程當中,標記完當前的根,那麼這個根下面的內容就不會被暫停恢復運行了,而移動過程當中,經過讀屏障來看這個內存是否是發生移動,若是在移動稍微停一下,移動過去後再使用,hotspot還沒這麼厲害,暫停時間仍是挺長的,只是相對其餘的GC策略在面對大內存來說是不錯的選擇。
下面看一些CMS的策略(併發GC總時間會比常規的並行GC長,由於它是在運行時去作GC,不少資源徵用都會影響其GC的效率,而整體的暫停時間會短暫不少不少,其並行線程數默認爲:(上面設置的並行線程數 + 3)/ 4
付:CMS是目前Hotspot管理大內存最好的JVM,若是是常規的JVM,最佳選擇爲ParallelOldGC,若是必需要以響應時間爲準,則選擇CMS,不過CMS有兩個隱藏的隱患:
一、CMS GC雖然是併發且並行運行的GC,可是初始化的時候若是採用默認值92%(JVM 1.5的白皮書上描述爲68%實際上是錯誤的,1.6是正確的),就很容易出現問題,由於CMS GC僅僅針對Old區域,Yong區域使用ParNew算法,也就是Old的CMS回收和Yong的回收能夠同時進行,也就是回收過程當中Yong有可能會晉升對象Old,而且業務也能夠同時運行,因此92%基本開始啓動CMS GC頗有可能old的內存就不夠用了,當內存不夠用的時候,就啓動Full GC,而且這個Full GC是串行的,因此若是弄的很差,CMS會比並行GC更加慢,爲何要啓用串行是由於CMS GC、並行GC、串行GC的繼承關係決定的,簡單說就是它沒辦法去調用並行GC的代碼,細節說後續有文章來細節說明),建議這個值設置爲70%左右吧,不過具體時間仍是本身決定。
二、CMS GC另外一個大的隱患,其實不看也差很少應該清楚,看名字就知道,就是不會作Compact操做,它最噁心的地方也在這裏,因此上面才說通常的應用都不使用它,它只有內存垃圾很是多,多得沒法分配晉升的空間的時候纔會出現一次compact,可是這個是Full GC,也就是上面的串行,很恐怖的,因此內存不是很大的,不要考慮使用它,並且它的算法十分複雜。
還有一些小的隱患是:和應用一塊兒徵用CPU(不過這個不是大問題,增長CPU便可)、整個運行過程當中時間比並行GC長(這個也不是大問題,由於咱們更加關心暫停時間而不是運行時間,由於暫停會影響很是多的業務)。
啓動CMS爲全局GC方法(注意這個參數也不能上面的並行GC進行混淆,Yong默認是並行的,上面已經說過
-XX:+UseConcMarkSweepGC
在併發GC下啓動增量模式,只能在CMS GC下這個參數纔有效。
-XX:+CMSIncrementalMode
啓動自動調節duty cycle,即在CMS GC中發生的時間比率設置,也就是說這段時間內最大容許發生多長時間的GC工做是能夠調整的。
-XX:+CMSIncrementalPacing
在上面這個參數設定後能夠分別設置如下兩個參數(參數設置的比率,範圍爲0-100):
-XX:CMSIncrementalDutyCycleMin=0
-XX:CMSIncrementalDutyCycle=10
增量GC上還有一個保護因子(CMSIncrementalSafetyFactor),不太經常使用;CMSIncrementalOffset提供增量GC連續時間比率的設置;CMSExpAvgFactor爲增量併發的GC增長權重計算。
-XX:CMSIncrementalSafetyFactor=
-XX:CMSIncrementalOffset=
-XX:CMSExpAvgFactor=
是否啓動並行CMS GC(默認也是開啓的)
-XX:+CMSParallelRemarkEnabled
要單獨對CMS GC設置並行線程數就設置(默認也不須要設置):
-XX:ParallelCMSThreads
對PernGen進行垃圾回收:
JDK 1.5在CMS GC基礎上須要設置參數(也就是前提是CMS GC纔有):
-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled
1.6之後的版本無需設置:-XX:+CMSPermGenSweepingEnabled,注意,其實一直以來Full GC都會觸發對Perm的回收過程,CMS GC須要有一些特殊照顧,雖然VM會對這塊區域回收,可是Perm回收的條件幾乎不太可能實現,首先須要這個類的classloader必須死掉,才能夠將該classloader下全部的class幹掉,也就是要麼所有死掉,要麼所有活着;另外,這個classloader下的class沒有任何object在使用,這個也太苛刻了吧,由於常規的對象申請都是經過系統默認的,應用服務器也有本身默認的classloader,要讓它死掉可能性不大,若是這都死掉了,系統也應該快掛了。
CMS GC由於是在程序運行時進行GC,不會暫停,因此不能等到不夠用的時候纔去開啓GC,官方說法是他們的默認值是68%,可是惋惜的是文檔寫錯了,通過不少測試和源碼驗證這個參數應該是在92%的時候被啓動,雖然還有8%的空間,可是仍是很可憐了,當CMS發現內存實在不夠的時候又回到常規的並行GC,因此不少人在沒有設置這個參數的時候發現CMS GC並無神馬優點嘛,和並行GC一個鳥樣子甚至於更加慢,因此這個時候須要設置參數(這個參數在上面已經說過,啓動CMS必定要設置這個參數):
-XX:CMSInitiatingOccupancyFraction=70
這樣保證Old的內存在使用到70%的時候,就開始啓動CMS了;若是你真的想看看默認值,那麼就使用參數:-XX:+PrintCMSInitiationStatistics 這個變量只有JDK 1.6可使用 1.5不能夠,查看實際值-XX:+PrintCMSStatistics;另外,還能夠設置參數-XX:CMSInitiatingPermOccupancyFraction來設置Perm空間達到多少時啓動CMS GC,不過意義不大。
JDK 1.6之後有些時候啓動CMS GC是根據計算代價進行啓動,也就是不必定按照你指定的參數來設置的,若是你不想讓它按照所謂的成原本計算GC的話,那麼你就使用一個參數:-XX:+UseCMSInitiatingOccupancyOnly,默認是false,它就只會按照你設置的比率來啓動CMS GC了。若是你的程序中有System.gc以及設置了ExplicitGCInvokesConcurrent在jdk 1.6中,這種狀況使用NIO是有可能產生問題的。
啓動CMS GC的compation操做,也就是發生多少次後作一次全局的compaction:
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction:發生多少次CMS Full GC,這個參數最好不要設置,由於要作compaction的話,也就是真正的Full GC是串行的,很是慢,讓它本身去決定何時須要作compaction。
-XX:CMSMaxAbortablePrecleanTime=5000 設置preclean步驟的超時時間,單位爲毫秒,preclean爲cms gc其中一個步驟,關於cms gc步驟比較多,本文就不細節探討了。
並行GC在mark階段,可能會同時發生minor GC,old區域也可能發生改變,因而併發GC會對發生了改變的內容進行remark操做,這個觸發的條件是:
-XX:CMSScheduleRemarkEdenSizeThreshold
-XX:CMSScheduleRemarkEdenPenetration
即Eden區域多大的時候開始觸發,和eden使用量超過百分比多少的時候觸發,前者默認是2M,後者默認是50%。
可是若是長期不作remark致使old作不了,能夠設置超時,這個超時默認是5秒,能夠經過參數:
-XX:CMSMaxAbortablePrecleanTime
-XX:+ExplicitGCInvokesConcurrent 在顯示發生GC的時候,容許進行並行GC。
-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses 幾乎和上面同樣,只不過多一個對Perm區域的回收而已。
補充:
其實JVM還有不少的版本,不少的廠商,與其優化的原則,隨便舉兩個例子hotspot在GC中作的一些優化(這裏不說代碼的編譯時優化或運行時優化):
Eden申請的空間對象由Old區域的某個對象的一個屬性指向(也就是Old區域的這個空間不回收,Eden這塊就沒有必要考慮回收),因此Hotspot在CPU寫上面,作了一個屏障,當發生賦值語句的時候(對內存來說賦值就是一種寫操做),若是發現是一個新的對象由Old指向Eden,那麼就會將這個對象記錄在一個卡片機裏面,這個卡片機是有不少512字節的卡片組成,當在YGC過程當中,就基本不會去移動或者管理這塊對象(付:這種卡片機會在CMS GC的算法中使用,不過和這個卡片不是放在同一個地方的,也是CMS GC的關鍵,對於CMS GC的算法細節描述,後續文章咱們單獨說明)。
Old區域對於一些比較大的對象,JVM就不會去管理個對象,也就是compact過程當中不會去移動這塊對象的區域等等吧。
以上大部分參數爲hotspot的自帶關於性能的參數,參考版本爲JDK 1.5和1.6的版本,不少爲我的經驗說明,不足以說明全部問題,若是有問題,歡迎探討;另外,JDK的參數是否是就只有這些呢,確定並非,我知道的也不止這些,可是有些以爲不必說出來的參數和一些數學運算的參數我就不想給出來了,好比像禁用掉GC的參數有神馬意義,咱們的服務器要是把這個禁用掉幹個屁啊,呵呵,作測試還能夠用這玩玩,讓它不作GC直接溢出;還有一些什麼計算因子啥的,還有不少複雜的數學運算規則,要是把這個配置明白了,就太那個了,並且通常狀況下也沒那個必要,JDK到如今的配置參數多達上500個以上,要知道完的話慢慢看吧,不過意義不大,並且要知道默認值最靠譜的是看源碼而不是看文檔,官方文檔也只能保證絕大部是正確的,不能保證全部的是正確的。