本文主要内容:
trace跟蹤參數
堆的配置設定參數
棧的配置設定參數
零、在ide的背景列印gc日志:
既然學習jvm,閱讀gc日志是處理java虛拟機記憶體問題的基礎技能,它隻是一些人為确定的規則,沒有太多技術含量。
既然如此,那麼在ide的控制台列印gc日志是必不可少的了。現在就告訴你怎麼列印。
(1)如果你用的是eclipse,列印gc日志的操作如下:

在上圖的箭頭處加上-xx:+printgcdetails這句話。于是,運作程式後,gc日志就可以列印出來了:
(2)如果你用的是intellij idea,列印gc日志的操作如下:
當然了,光有-xx:+printgcdetails這一句參數肯定是不夠的,下面我們詳細介紹一下更多的參數配置。
一、trace跟蹤參數:
1、列印gc的簡要資訊:
解釋:可以列印gc的簡要資訊。比如:
[gc 4790k->374k(15872k), 0.0001606 secs]
[gc 4790k->374k(15872k), 0.0001474 secs]
[gc 4790k->374k(15872k), 0.0001563 secs]
[gc 4790k->374k(15872k), 0.0001682 secs]
上方日志的意思是說,gc之前,用了4m左右的記憶體,gc之後,用了374k記憶體,一共回收了将近4m。記憶體大小一共是16m左右。
2、列印gc的詳細資訊:
解釋:列印gc詳細資訊。
解釋:列印cg發生的時間戳。
了解gc日志的含義:
例如下面這段日志:
[gc[defnew: 4416k->0k(4928k), 0.0001897 secs] 4790k->374k(15872k), 0.0002232 secs] [times: user=0.00 sys=0.00, real=0.00 secs]
上方日志的意思是說:這是一個新生代的gc。方括号内部的“4416k->0k(4928k)”含義是:“gc前該記憶體區域已使用容量->gc後該記憶體區域已使用容量(該記憶體區域總容量)”。而在方括号之外的“4790k->374k(15872k)”表示“gc前java堆已使用容量->gc後java堆已使用容量(java堆總容量)”。
再往後看,“0.0001897 secs”表示該記憶體區域gc所占用的時間,機關是秒。
再比如下面這段gc日志:
上圖中,我們先看一下用紅框标注的“[0x27e80000, 0x28d80000, 0x28d80000)”的含義,它表示新生代在記憶體當中的位置:第一個參數是申請到的起始位置,第二個參數是申請到的終點位置,第三個參數表示最多能申請到的位置。上圖中的例子表示新生代申請到了15m的控件,而這個15m是等于:(eden space的12288k)+(from space的1536k)+(to space的1536k)。
疑問:配置設定到的新生代有15m,但是可用的隻有13824k,為什麼會有這個差異呢?等我們在後面的文章中學習到了gc算法之後就明白了。
3、指定gc log的位置:
解釋:指定gc log的位置,以檔案輸出。幫助開發人員分析問題。
解釋:每一次gc前和gc後,都列印堆資訊。
例如:
上圖中,紅框部分正好是一次gc,紅框部分的前面是gc之前的日志,紅框部分的後面是gc之後的日志。
解釋:監控類的加載。
[loaded java.lang.object from shared objects file] [loaded java.io.serializable from shared objects file] [loaded java.lang.comparable from shared objects file] [loaded java.lang.charsequence from shared objects file] [loaded java.lang.string from shared objects file] [loaded java.lang.reflect.genericdeclaration from shared objects file] [loaded java.lang.reflect.type from shared objects file]
解釋:按下ctrl+break後,列印類的資訊。
二、堆的配置設定參數:
1、-xmx –xms:指定最大堆和最小堆
舉例、當參數設定為如下時:
然後我們在程式中運作如下代碼:
運作效果:
保持參數不變,在程式中運作如下代碼:(配置設定1m空間給數組)
運作效果:
注:java會盡可能将total mem的值維持在最小堆。
保持參數不變,在程式中運作如下代碼:(配置設定10m空間給數組)
如上圖紅框所示:此時,total mem 為7m時已經不能滿足需求了,于是total mem漲成了16.5m。
保持參數不變,在程式中運作如下代碼:(進行一次gc的回收)
問題1: -xmx(最大堆空間)和 –xms(最小堆空間)應該保持一個什麼關系,可以讓系統的性能盡可能的好呢?
問題2:如果你要做一個java的桌面産品,需要綁定jre,但是jre又很大,你如何做一下jre的瘦身呢?
2、-xmn、-xx:newratio、-xx:survivorratio:
-xmn
設定新生代大小
-xx:newratio
新生代(eden+2*s)和老年代(不包含永久區)的比值
例如:4,表示新生代:老年代=1:4,即新生代占整個堆的1/5
-xx:survivorratio(幸存代)
設定兩個survivor區和eden的比值
例如:8,表示兩個survivor:eden=2:8,即一個survivor占年輕代的1/10
現在運作如下這段代碼:
我們通過設定不同的jvm參數,來看一下gc日志的差別。
(1)當參數設定為如下時:(設定新生代為1m,很小)
總結:
沒有觸發gc
由于新生代的記憶體比較小,是以全部配置設定在老年代。
(2)當參數設定為如下時:(設定新生代為15m,足夠大)
上圖顯示:
沒有觸發gc 全部配置設定在eden(藍框所示) 老年代沒有使用(紅框所示)
(3)當參數設定為如下時:(設定新生代為7m,不大不小)
進行了2次新生代gc
s0 s1 太小,需要老年代擔保
(4)當參數設定為如下時:(設定新生代為7m,不大不小;同時,增加幸存代大小)
進行了至少3次新生代gc
s0 s1 增大
(5)當參數設定為如下時:
(6)當參數設定為如下時: 和上面的(5)相比,适當減小幸存代大小,這樣的話,能夠減少gc的次數
3、-xx:+heapdumponoutofmemoryerror、-xx:+heapdumppath
-xx:+heapdumponoutofmemoryerror
oom時導出堆到檔案
根據這個檔案,我們可以看到系統dump時發生了什麼。
-xx:+heapdumppath
導出oom的路徑
例如我們設定如下的參數:
上方意思是說,現在給堆記憶體最多配置設定20m的空間。如果發生了oom異常,那就把dump資訊導出到d:/a.dump檔案中。
然後,我們執行如下代碼:
上方代碼中,需要利用25m的空間,很顯然會發生oom異常。現在我們運作程式,控制台列印如下:
現在我們去d盤看一下dump檔案:
上圖顯示,一般來說,這個檔案的大小和最大堆的大小保持一緻。
我們可以用visualvm打開這個dump檔案。
注:關于visualvm的使用,可以參考下面這篇部落格:
或者使用java自帶的java visualvm工具也行:
上圖中就是dump出來的檔案,檔案中可以看到,一共有19個byte已經被配置設定了。
4、-xx:onoutofmemoryerror:
-xx:onoutofmemoryerror
在oom時,執行一個腳本。
可以在oom時,發送郵件,甚至是重新開機程式。
上方參數的意思是說,執行printstack.bat腳本,而這個腳本做的事情是:d:/tools/jdk1.7_40/bin/jstack -f %1 > d:/a.txt,即當程式oom時,在d:/a.txt中将會生成線程的dump。
5、堆的配置設定參數總結:
根據實際事情調整新生代和幸存代的大小
官方推薦新生代占堆的3/8
幸存代占新生代的1/10
在oom時,記得dump出堆,確定可以排查現場問題
6、永久區配置設定參數:
-xx:permsize -xx:maxpermsize
設定永久區的初始空間和最大空間。也就是說,jvm啟動時,永久區一開始就占用了permsize大小的空間,如果空間還不夠,可以繼續擴充,但是不能超過maxpermsize,否則會oom。
他們表示,一個系統可以容納多少個類型
代碼舉例:
我們知道,使用cglib等庫的時候,可能會産生大量的類,這些類,有可能撐爆永久區導緻oom。于是,我們運作下面這段代碼:
上面這段代碼會在永久區不斷地産生新的類。于是,運作效果如下:
如果堆空間沒有用完也抛出了oom,有可能是永久區導緻的。
堆空間實際占用非常少,但是永久區溢出 一樣抛出oom。
三、棧的配置設定參數:
1、xss:
設定棧空間的大小。通常隻有幾百k 決定了函數調用的深度 每個線程都有獨立的棧空間 局部變量、參數 配置設定在棧上
注:棧空間是每個線程私有的區域。棧裡面的主要内容是棧幀,而棧幀存放的是局部變量表,局部變量表的内容是:局部變量、參數。
我們來看下面這段代碼:(沒有出口的遞歸調用)
上方這段代碼是沒有出口的遞歸調用,肯定會出現oom的。
如果設定棧大小為128k:
運作效果如下:(方法被調用了294次)
如果設定棧大小為256k:(方法被調用748次)
意味着函數調用的次數太深,像這種遞歸調用就是個典型的例子。
我們在本文中介紹了jvm的一些最基本的參數,還有很多參數(如gc參數等)将在後續的系列文章中進行介紹。我們将在接下來的文章中介紹gc算法。