Java虛擬機詳解03----經常使用JVM配置參數

 

【聲明】 html

歡迎轉載,但請保留文章原始出處→_→ java

生命壹號:http://www.cnblogs.com/smyhvae/算法

文章來源:http://www.cnblogs.com/smyhvae/p/4736162.html
數組

 

本文主要內容:jvm

  • Trace跟蹤參數
  • 堆的分配參數
  • 棧的分配參數

 

零、在IDE的後臺打印GC日誌:函數

既然學習JVM,閱讀GC日誌是處理Java虛擬機內存問題的基礎技能,它只是一些人爲肯定的規則,沒有太多技術含量。工具

既然如此,那麼在IDE的控制檯打印GC日誌是必不可少的了。如今就告訴你怎麼打印。性能

(1)若是你用的是Eclipse,打印GC日誌的操做以下:學習

d32742cf-b002-4c55-a185-d4ccdc90a69c

bc5b8afb-9d1f-438b-9225-ee7fbbbe2454

在上圖的箭頭處加上-XX:+PrintGCDetails這句話。因而,運行程序後,GC日誌就能夠打印出來了:spa

25d80649-69f0-47b2-a3bb-418ba4457849

(2)若是你用的是IntelliJ IDEA,打印GC日誌的操做以下:

94726055-e81f-45b8-8978-d1277c5acb17

f2c896da-404c-4415-98ef-5b582dec3528

在上圖的箭頭處加上-XX:+PrintGCDetails這句話。因而,運行程序後,GC日誌就能夠打印出來了:

6b1b4352-7172-4404-ac6c-b94c16036d73

固然了,光有-XX:+PrintGCDetails這一句參數確定是不夠的,下面咱們詳細介紹一下更多的參數配置。

 

1、Trace跟蹤參數:

一、打印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日誌:

1fe41f36-cc6b-4a8b-b48e-8cbe2e3a04af

上圖中,咱們先看一下用紅框標註的「[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的位置,以文件輸出。幫助開發人員分析問題。

805e8e33-1e3b-46c0-af9d-d68f4d38816f

  

-XX:+PrintHeapAtGC

解釋:每一次GC前和GC後,都打印堆信息。

例如:

1c6f3837-4b31-4ac2-a639-e79c92f80df5

上圖中,紅框部分正好是一次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後,打印類的信息。

例如:

c8050739-0029-47cd-95bd-fbbd6289a5d1

 

2、堆的分配參數:

一、-Xmx –Xms指定最大堆和最小堆

舉例、當參數設置爲以下時:

-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"); //當前可用的總空間

 運行效果:

79c1029d-58fe-47d9-aa2e-1c5ee7e741cd

保持參數不變,在程序中運行以下代碼:(分配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");

運行效果:

14d260c9-28bf-4544-a36f-ee14a1d59623

注: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"); //當前可用的總空間

運行效果:

284e8036-8d70-46bc-aac1-99c9b3deb3ef

如上圖紅框所示:此時,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"); //當前可用的總空間 

運行效果:

e419c020-0da3-4046-9b7f-f542ee14a780

問題1: -Xmx(最大堆空間)和 –Xms(最小堆空間)應該保持一個什麼關係,可讓系統的性能儘量的好呢?

問題2:若是你要作一個Java的桌面產品,須要綁定JRE,可是JRE又很大,你如何作一下JRE的瘦身呢?

 

二、-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

 

如今運行以下這段代碼:

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 

運行效果:

4f0b24b4-cc74-4fd6-af15-b30a784d351b

總結:

  沒有觸發GC

    因爲新生代的內存比較小,因此所有分配在老年代。

 

(2)當參數設置爲以下時:(設置新生代爲15M,足夠大)

-Xmx20m -Xms20m -Xmn15m -XX:+PrintGCDetails

運行效果:

2cb6145f-8c1b-4269-bcfa-31912d2f0d41

上圖顯示:

沒有觸發GC

所有分配在eden(藍框所示)

老年代沒有使用(紅框所示)

 

(3)當參數設置爲以下時:(設置新生代爲7M,不大不小)

-Xmx20m -Xms20m –Xmn7m -XX:+PrintGCDetails

運行效果:

0e0cc65d-e291-477a-ba7f-7d433f1085cc

總結:

  進行了2次新生代GC

  s0 s1 過小,須要老年代擔保

 

(4)當參數設置爲以下時:(設置新生代爲7M,不大不小;同時,增長倖存代大小)

-Xmx20m -Xms20m -Xmn7m -XX:SurvivorRatio=2 -XX:+PrintGCDetails

運行效果:

35eb96d6-9251-45e5-8120-05b82210df06

總結:

    進行了至少3次新生代GC

    s0 s1 增大

 

(5)當參數設置爲以下時:

-Xmx20m -Xms20m -XX:NewRatio=1

-XX:SurvivorRatio=2 -XX:+PrintGCDetails 

運行效果:

c85f7057-1842-4d11-bc28-fc766e5681f8

 

 

(6)當參數設置爲以下時: 和上面的(5)相比,適當減少倖存代大小,這樣的話,可以減小GC的次數

-Xmx20m -Xms20m -XX:NewRatio=1

-XX:SurvivorRatio=3 -XX:+PrintGCDetails

fd3322ec-a853-49aa-86fa-81d8b3a02f8c

 

 

三、-XX:+HeapDumpOnOutOfMemoryError、-XX:+HeapDumpPath

  • -XX:+HeapDumpOnOutOfMemoryError

    OOM時導出堆到文件

      根據這個文件,咱們能夠看到系統dump時發生了什麼。

  • -XX:+HeapDumpPath

    導出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異常。如今咱們運行程序,控制檯打印以下:

3320aba5-2aa6-42bc-b656-57bbc5d8ec41

如今咱們去D盤看一下dump文件:

8782a0ae-62fb-43a8-a5a6-1c5691e7fa59

上圖顯示,通常來講,這個文件的大小和最大堆的大小保持一致。

咱們能夠用VisualVM打開這個dump文件。

注:關於VisualVM的使用,能夠參考下面這篇博客:

使用 VisualVM 進行性能分析及調優:http://www.ibm.com/developerworks/cn/java/j-lo-visualvm/

或者使用Java自帶的Java VisualVM工具也行:

f9158d50-95d0-4732-942c-e872181fa530

f69bd0d2-a355-4a93-81c1-c3e71bce7509

上圖中就是dump出來的文件,文件中能夠看到,一共有19個byte已經被分配了。 

 

四、-XX:OnOutOfMemoryError:

  • -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。

五、堆的分配參數總結:

  • 根據實際事情調整新生代和倖存代的大小
  • 官方推薦新生代佔堆的3/8
  • 倖存代佔新生代的1/10
  • 在OOM時,記得Dump出堆,確保能夠排查現場問題

 

六、永久區分配參數:

  • -XX:PermSize  -XX:MaxPermSize

    設置永久區的初始空間和最大空間。也就是說,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());
}

上面這段代碼會在永久區不斷地產生新的類。因而,運行效果以下:

fd7bcefb-d6d5-4fe0-8d77-9cddae2733fc

總結:

  若是堆空間沒有用完也拋出了OOM,有多是永久區致使的

    堆空間實際佔用很是少,可是永久區溢出 同樣拋出OOM。

 

3、棧的分配參數:

一、Xss:

設置棧空間的大小。一般只有幾百K

  決定了函數調用的深度

  每一個線程都有獨立的棧空間

  局部變量、參數 分配在棧上

注:棧空間是每一個線程私有的區域。棧裏面的主要內容是棧幀,而棧幀存放的是局部變量表,局部變量表的內容是:局部變量、參數。

咱們來看下面這段代碼:(沒有出口的遞歸調用)

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次)

5c2b2060-e54a-4e7c-9a30-81567204d55b

若是設置棧大小爲256k:(方法被調用748次)

7d6be7d6-b646-42bf-9357-1a3bccbb7a49

意味着函數調用的次數太深,像這種遞歸調用就是個典型的例子。

 

總結:

咱們在本文中介紹了jvm的一些最基本的參數,還有不少參數(如GC參數等)將在後續的系列文章中進行介紹。咱們將在接下來的文章中介紹GC算法。

 

手有玫瑰,贈人餘香。支付寶掃一掃吧:

相關文章
相關標籤/搜索