本文主要內容:html
既然學習JVM,閱讀GC日誌是處理Java虛擬機內存問題的基礎技能,它只是一些人爲肯定的規則,沒有太多技術含量。java
既然如此,那麼在IDE的控制檯打印GC日誌是必不可少的了。如今就告訴你怎麼打印。算法
(1)若是你用的是Eclipse,打印GC日誌的操做以下:數組
在上圖的箭頭處加上-XX:+PrintGCDetails這句話。因而,運行程序後,GC日誌就能夠打印出來了:jvm
(2)若是你用的是IntelliJ IDEA,打印GC日誌的操做以下:函數
在上圖的箭頭處加上-XX:+PrintGCDetails這句話。因而,運行程序後,GC日誌就能夠打印出來了:工具
固然了,光有-XX:+PrintGCDetails這一句參數確定是不夠的,下面咱們詳細介紹一下更多的參數配置。性能
一、打印GC的簡要信息:學習
-verbose:gc
-XX:+printGC
解釋:能夠打印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左右。
二、打印GC的詳細信息:
-XX:+PrintGCDetails
解釋:打印GC詳細信息。
-XX:+PrintGCTimeStamps
解釋:打印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算法以後就明白了。
三、指定GC log的位置:
-Xloggc:log/gc.log
解釋:指定GC log的位置,以文件輸出。幫助開發人員分析問題。
-XX:+PrintHeapAtGC
解釋:每一次GC前和GC後,都打印堆信息。
例如:
上圖中,紅框部分正好是一次GC,紅框部分的前面是GC以前的日誌,紅框部分的後面是GC以後的日誌。
-XX:+TraceClassLoading
解釋:監控類的加載。
例如:
[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]
-XX:+PrintClassHistogram
解釋:按下Ctrl+Break後,打印類的信息。
例如:
JVM 中最大堆大小有三方面限制:相關操做系統的數據模型(32-bt仍是64-bit)限制;系統的可用虛擬內存限制;系統的可用物理內存限制。32位操做系統雖然尋址空間大小是4G(2^32),但具體操做系統會給一個限制(通常Windows系統有2GB內核空間,故用戶空間限制在1.5G~2G,Linux系統有1GB內核空間,故用戶空間是2G~3G);64爲操做系統對內存無限制。全部線程共享數據區大小=新生代大小 + 年老代大小 + 持久代大小。持久代通常固定大小爲64m。因此java堆中增大年輕代後,將會減少年老代大小。此值對系統性能影響較大,Sun官方推薦配置爲java堆的3/8。
一、-Xmx –Xms:指定java堆最大值(默認值是物理內存的1/4(<1GB))和初始java堆最小值(默認值是物理內存的1/64(<1GB))
默認(MinHeapFreeRatio參數能夠調整)空餘堆內存小於40%時,JVM就會增大堆直到-Xmx的最大限制.,默認(MaxHeapFreeRatio參數能夠調整)空餘堆內存大於70%時,JVM會減小堆直到 -Xms的最小限制。開發過程當中,一般會將 -Xms 與 -Xmx兩個參數的配置相同的值,其目的是爲了可以在java垃圾回收機制清理完堆區後不須要從新分隔計算堆區的大小而浪費資源。
注意:此處設置的是Java堆大小,也就是新生代大小 + 年老代大小
舉例、當參數設置爲以下時:
-Xmx20m -Xms5m
而後咱們在程序中運行以下代碼:
System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); //系統的最大空間
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系統的空閒空間
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M"); //當前可用的總空間
運行效果:
保持參數不變,在程序中運行以下代碼:(分配1M空間給數組)
byte[] b = new byte[1 * 1024 * 1024]; System.out.println("分配了1M空間給數組");
System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); //系統的最大空間
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系統的空閒空間
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");
運行效果:
注:Java會盡量將total mem的值維持在最小堆。
保持參數不變,在程序中運行以下代碼:(分配10M空間給數組)
byte[] b = new byte[10 * 1024 * 1024]; System.out.println("分配了10M空間給數組");
System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); //系統的最大空間
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系統的空閒空間
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M"); //當前可用的總空間
運行效果:
如上圖紅框所示:此時,total mem 爲7M時已經不能知足需求了,因而total mem漲成了16.5M。
保持參數不變,在程序中運行以下代碼:(進行一次GC的回收)
System.gc();
System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); //系統的最大空間
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系統的空閒空間
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M"); //當前可用的總空間
運行效果:
二、-Xmn、-XX:NewRatio、-XX:SurvivorRatio、-XXNewSize、-XX:MaxNewSize:
設置新生代大小,大小是:eden+ 2 survivor space
全部共享數據區大小=年輕代大小 + 年老代大小 + 持久代大小。通常永久代大小固定,因此增大年輕代後,將會減少年老代大小,此值對系統性能影響較大,Sun官方推薦配置爲整個java堆的3/8
新生代(eden+2*Survivor)和老年代(不包含永久區)的比值
例如:-XX:NewRatio=4,表示新生代:老年代=1:4,即新生代佔整個堆的1/5。在Xms=Xmx而且設置了Xmn的狀況下,該參數不須要進行設置。
設置兩個Survivor區和eden的比值
例如:8,表示兩個Survivor:eden=2:8,即一個Survivor佔年輕代的1/10
設置年輕代大小
設置年輕代最大值
如今運行以下這段代碼:
public class JavaTest { public static void main(String[] args) { byte[] b = null; for (int i = 0; i < 10; i++) b = new byte[1 * 1024 * 1024]; } }
咱們經過設置不一樣的jvm參數,來看一下GC日誌的區別。
(1)當參數設置爲以下時:(設置新生代爲1M,很小)
-Xmx20m -Xms20m -Xmn1m -XX:+PrintGCDetails
運行效果:
總結:
沒有觸發GC
因爲新生代的內存比較小,因此所有分配在老年代。
(2)當參數設置爲以下時:(設置新生代爲15M,足夠大)
-Xmx20m -Xms20m -Xmn15m -XX:+PrintGCDetails
運行效果:
上圖顯示:
沒有觸發GC
所有分配在eden(藍框所示)
老年代沒有使用(紅框所示)
(3)當參數設置爲以下時:(設置新生代爲7M,不大不小)
-Xmx20m -Xms20m –Xmn7m -XX:+PrintGCDetails
運行效果:
總結:
進行了2次新生代GC
s0 s1 過小,須要老年代擔保
(4)當參數設置爲以下時:(設置新生代爲7M,不大不小;同時,增長倖存代大小)
-Xmx20m -Xms20m -Xmn7m -XX:SurvivorRatio=2 -XX:+PrintGCDetails
運行效果:
總結:
進行了至少3次新生代GC
s0 s1 增大
(5)當參數設置爲以下時:
-Xmx20m -Xms20m -XX:NewRatio=1 -XX:SurvivorRatio=2 -XX:+PrintGCDetails
運行效果:
(6)當參數設置爲以下時: 和上面的(5)相比,適當減少倖存代大小,這樣的話,可以減小GC的次數
-Xmx20m -Xms20m -XX:NewRatio=1 -XX:SurvivorRatio=3 -XX:+PrintGCDetails
三、-XX:+HeapDumpOnOutOfMemoryError、-XX:+HeapDumpPath
OOM時導出堆到文件
根據這個文件,咱們能夠看到系統dump時發生了什麼。
導出OOM的路徑
例如咱們設置以下的參數:
-Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/a.dump
上方意思是說,如今給堆內存最多分配20M的空間。若是發生了OOM異常,那就把dump信息導出到d:/a.dump文件中。
而後,咱們執行以下代碼:
Vector v = new Vector(); for (int i = 0; i < 25; i++) v.add(new byte[1 * 1024 * 1024]);
上方代碼中,須要利用25M的空間,很顯然會發生OOM異常。如今咱們運行程序,控制檯打印以下:
如今咱們去D盤看一下dump文件:
上圖顯示,通常來講,這個文件的大小和最大堆的大小保持一致。
咱們能夠用VisualVM打開這個dump文件。
注:關於VisualVM的使用,能夠參考下面這篇博客:
使用 VisualVM 進行性能分析及調優:http://www.ibm.com/developerworks/cn/java/j-lo-visualvm/
或者使用Java自帶的Java VisualVM工具也行:
上圖中就是dump出來的文件,文件中能夠看到,一共有19個byte已經被分配了。
四、-XX:OnOutOfMemoryError:
在OOM時,執行一個腳本。
能夠在OOM時,發送郵件,甚至是重啓程序。
例如咱們設置以下的參數:
-XX:OnOutOfMemoryError=D:/tools/jdk1.7_40/bin/printstack.bat %p //p表明的是當前進程的pid
上方參數的意思是說,執行printstack.bat腳本,而這個腳本作的事情是:D:/tools/jdk1.7_40/bin/jstack -F %1 > D:/a.txt,即當程序OOM時,在D:/a.txt中將會生成線程的dump。
五、堆的分配參數總結:
六、永久區分配參數:
設置永久區的初始空間(默認爲物理內存的1/64)和最大空間(默認爲物理內存的1/4)。也就是說,jvm啓動時,永久區一開始就佔用了PermSize大小的空間,若是空間還不夠,能夠繼續擴展,可是不能超過MaxPermSize,不然會OOM。
他們表示,一個系統能夠容納多少個類型
代碼舉例:
咱們知道,使用CGLIB等庫的時候,可能會產生大量的類,這些類,有可能撐爆永久區致使OOM。因而,咱們運行下面這段代碼:
for(int i=0;i<100000;i++){ CglibBean bean = new CglibBean("geym.jvm.ch3.perm.bean"+i,new HashMap()); }
上面這段代碼會在永久區不斷地產生新的類。因而,運行效果以下:
總結:
若是堆空間沒有用完也拋出了OOM,有多是永久區致使的。
堆空間實際佔用很是少,可是永久區溢出 同樣拋出OOM。
一、-Xss:
設置每一個線程棧空間的大小。JDK5.0之後每一個線程堆棧大小爲1M,之前每一個線程堆棧大小爲256K。在相同物理內存下,減少這個值能生成更多的線程。可是操做系統對一個進程內的線程數仍是有限制的,不能無限生成,經驗值在3000~5000左右
決定了函數調用的深度
每一個線程都有獨立的棧空間
局部變量、參數 分配在棧上
注:棧空間是每一個線程私有的區域。棧裏面的主要內容是棧幀,而棧幀存放的是局部變量表,局部變量表的內容是:局部變量、參數。
咱們來看下面這段代碼:(沒有出口的遞歸調用)
public class TestStackDeep { private static int count = 0;
public static void recursion(long a, long b, long c) { long e = 1, f = 2, g = 3, h = 4, i = 5, k = 6, q = 7, x = 8, y = 9, z = 10; count++; recursion(a, b, c); }
public static void main(String args[]) { try { recursion(0L, 0L, 0L); } catch (Throwable e) { System.out.println("deep of calling = " + count); e.printStackTrace(); } } }
上方這段代碼是沒有出口的遞歸調用,確定會出現OOM的。
若是設置棧大小爲128k:
-Xss128K
運行效果以下:(方法被調用了294次)
若是設置棧大小爲256k:(方法被調用748次)
意味着函數調用的次數太深,像這種遞歸調用就是個典型的例子。
二、-XXThreadStackSize:
設置線程棧的大小(0 means use default stack size)
一、-XXThreadStackSize:
設置內存頁的大小,不可設置過大,會影響Perm的大小
二、-XX:+UseFastAccessorMethods:
設置原始類型的快速優化
三、-XX:+DisableExplicitGC:
設置關閉System.gc()(這個參數須要嚴格的測試)
四、-XX:MaxTenuringThreshold
設置垃圾最大年齡。若是設置爲0的話,則年輕代對象不通過Survivor區,直接進入年老代. 對於年老代比較多的應用,能夠提升效率。若是將此值設置爲一個較大值,則年輕代對象會在Survivor區進行屢次複製,這樣能夠增長對象再年輕代的存活時間,增長在年輕代即被回收的機率。該參數只有在串行GC時纔有效.
五、-XX:+AggressiveOpts
加快編譯
六、-XX:+UseBiasedLocking
鎖機制的性能改善
七、-Xnoclassgc
禁用垃圾回收
八、-XX:SoftRefLRUPolicyMSPerMB
設置每兆堆空閒空間中SoftReference的存活時間,默認值是1s 。(softly reachable objects will remain alive for some amount of time after the last time they were referenced. The default value is one second of lifetime per free megabyte in the heap)
九、-XX:PretenureSizeThreshold
設置對象超過多大時直接在舊生代分配,默認值是0。
十、-XX:TLABWasteTargetPercent
設置TLAB佔eden區的百分比,默認值是1% 。
十一、-XX:+CollectGen0First
設置FullGC時是否先YGC,默認值是false。
原文出處:http://www.cnblogs.com/smyhvae/p/4736162.html
參考文獻:http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html(含JVM詳細參數說明)
http://www.cnblogs.com/edwardlauxh/archive/2010/04/25/1918603.html