1. 程式人生 > >Java虛擬機器詳解03----常用JVM配置引數

Java虛擬機器詳解03----常用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日誌就可以打印出來了:

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這一句引數肯定是不夠的,下面我們詳細介紹一下更多的引數配置。

一、Trace跟蹤引數:

1、列印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左右。

2、列印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演算法之後就明白了。

3、指定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

二、堆的分配引數:

1、-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的瘦身呢?

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

現在執行如下這段程式碼:

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

3、-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的使用,可以參考下面這篇部落格:

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

f9158d50-95d0-4732-942c-e872181fa530

f69bd0d2-a355-4a93-81c1-c3e71bce7509

上圖中就是dump出來的檔案,檔案中可以看到,一共有19個byte已經被分配了。 

4、-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。

5、堆的分配引數總結:

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

6、永久區分配引數:

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

三、棧的分配引數:

1、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演算法。

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

相關推薦

Java虛擬機器03----常用JVM配置引數

本文主要內容: Trace跟蹤引數 堆的分配引數 棧的分配引數 零、在IDE的後臺列印GC日誌: 既然學習JVM,閱讀GC日誌是處理Java虛擬機器記憶體問題的基礎技能,它只是一些人為確定的規則,沒有太多技術含量。 既然如此,那麼在I

Java虛擬機器探究】5.常用JVM配置引數-棧的分配引數

在使用JVM編譯java時,都會去設定相關的引數(例如使用eclipse的時候,可以設定eclipse的eclipse.ini檔案來對jvm做一些引數配置)。jvm的引數設定主要涉及到三種,分別是Trace跟蹤引數、堆的分配引數、棧的分配引數。本章主要講解棧的分配引數的相關資

Java虛擬機器----常用JVM配置引數

原文地址:http://www.cnblogs.com/smyhvae/p/4736162.html 本文主要內容: Trace跟蹤引數堆的分配引數棧的分配引數 零、在IDE的後臺列印GC日誌: 既然學習JVM,閱讀GC日誌是處理Java虛擬機器記憶體問題的

JVM內幕:Java虛擬機器

這篇文章解釋了Java 虛擬機器(JVM)的內部架構。下圖顯示了遵守 Java SE 7 規範的典型的 JVM 核心內部元件。 上圖顯示的元件分兩個章節解釋。第一章討論針對每個執行緒建立的元件,第二章節討論了執行緒無關元件。 執行緒 JVM 系統執行緒

Java虛擬機器----JVM記憶體結構

http://www.cnblogs.com/smyhvae/p/4748392.htm   主要內容如下: JVM啟動流程 JVM基本結構 記憶體模型 編譯和解釋執行的概念   一、JVM啟動流程: JVM啟動時,是由java命令/javaw命令來啟

Java虛擬機器----JVM常見問題總結

【宣告】  歡迎轉載,但請保留文章原始出處→_→  【正文】 宣告:本文只是做一個總結,有關jvm的詳細知識可以參考本人之前的系列文章,尤其是那篇:Java虛擬機器詳解04----GC演算法和種類。那篇文章和本文是面試時的重點。 面試必問關鍵詞:JVM垃圾回

Java虛擬機器(五)------JVM引數(持續更新)

  JVM引數有很多,其實我們直接使用預設的JVM引數,不去修改都可以滿足大多數情況。但是如果你想在有限的硬體資源下,部署的系統達到最大的執行效率,那麼進行相關的JVM引數設定是必不可少的。下面我們就來對這些JVM引數進行詳細的介紹。   JVM引數主要分為以下三種(可以根據書寫形式來區分): 1、標準引

Java虛擬01----初識JVM

日誌 可變 lar 反射 開始 rac ibm java語言 lan 主要內容如下: JVM的概念 JVM發展歷史 JVM種類 Java語言規範 JVM規範 一、JVM的概念: JVM:   Java Virtual Machine,意為Java虛擬機。 虛擬機:   

Java虛擬機器04----GC演算法和種類【重要】

【宣告】  歡迎轉載,但請保留文章原始出處→_→  本文主要內容: GC的概念 GC演算法     引用計數法(無法解決迴圈引用的問題,不被java採納)       根搜尋演算法       現代虛擬機

Java虛擬機器----GC演算法和種類【重要】

轉載自:http://www.cnblogs.com/smyhvae/p/4744233.html 本文主要內容: GC的概念GC演算法    引用計數法(無法解決迴圈引用的問題,不被java採納)       根搜尋演算法       現代虛擬機器中的垃圾蒐集演算法:

Java虛擬機器(一)------簡介

  本系列部落格我們將以當前預設的主流虛擬機器HotSpot 為例,詳細介紹 Java虛擬機器。以 JDK1.7 為主,同時介紹與 JDK1.8 的不同之處,通過Oracle官網以及各種文獻進行整理,並加以驗證,力求保證這塊知識的正確性,完整性。   以下是本系列部落格參考的相關文件:   ①、JDK1.

Java虛擬機器(二)------執行時記憶體結構

  首先通過一張圖瞭解 Java程式的執行流程:      我們編寫好的Java原始碼程式,通過Java編譯器javac編譯成Java虛擬機器識別的class檔案(位元組碼檔案),然後由 JVM 中的類載入器載入編譯生成的位元組碼檔案,載入完畢之後再由 JVM 執行引擎去執行。在載入完畢到執行過程中,J

Java虛擬機器(三)------垃圾回收

  如果對C++這門語言熟悉的人,再來看Java,就會發現這兩者對垃圾(記憶體)回收的策略有很大的不同。   C++:垃圾回收很重要,我們必須要自己來回收!!!   Java:垃圾回收很重要,我們必須交給系統來幫我們完成!!!   我想這也能看出這兩門語言設計者的心態吧,總之,Java和C++之間有一堵

Java虛擬機器(四)------垃圾收集器

  上一篇部落格我們介紹了Java虛擬機器垃圾回收,介紹了幾種常用的垃圾回收演算法,包括標記-清除,標記整理,複製等,這些演算法我們可以看做是記憶體回收的理論方法,那麼在Java虛擬機器中,由誰來具體實現這些方法呢?   沒錯,就是本篇部落格介紹的內容——垃圾收集器。 1、垃圾收集

Java虛擬機器(六)------記憶體分配

  我們說Java是自動進行記憶體管理的,所謂自動化就是,不需要程式設計師操心,Java會自動進行記憶體分配和記憶體回收這兩方面。   前面我們介紹過如何通過垃圾回收器來回收記憶體,那麼本篇部落格我們來聊聊如何進行分配記憶體。   物件的記憶體分配,往大方向上講,就是堆上進行分配(但也有可能經過JIT編譯

Java虛擬機器(七)------虛擬機器監控和分析工具(1)——命令列

  通過前面的幾篇部落格,我們介紹了Java虛擬機器的記憶體分配以及記憶體回收等理論知識,瞭解這些知識對於我們在實際生產環境中提高系統的執行效率是有很大的幫助的。但是話又說回來,在實際生產環境中,線上專案正在執行,我們怎麼去監控虛擬機器執行效率?又或者線上專案發生了OOM,異常堆疊資訊,我們又怎麼去抓取,然後

Java虛擬機器(八)------虛擬機器監控和分析工具(2)——視覺化

  上篇部落格我們介紹了虛擬機器監控和分析命令列工具,由於其不夠直觀,不是很容易排查問題,那麼本篇部落格我們就來介紹幾個視覺化工具。 1、JConsole   JConsole(Java Monitoring and Management Console)是一款基於 JMX 的視覺化監視和管理的工具。它管

Java虛擬機器(十)------類載入過程

  在上一篇文章中,我們詳細的介紹了Java類檔案結構,那麼這些Class檔案是如何被載入到記憶體,由虛擬機器來直接使用的呢?這就是本篇部落格將要介紹的——類載入過程。 1、類的生命週期   類從被載入到虛擬機器記憶體開始,到卸載出記憶體為止,其宣告週期流程如下:      上

Java虛擬機器(十一)------雙親委派模型

  在上一篇部落格,我們介紹了類載入過程,包括5個階段,分別是“載入”,“驗證”,“準備”,“解析”,“初始化”,如下圖所示:        本篇部落格,我們來介紹Java虛

我理解的JVM-----JavaJVM虛擬機器

很多大佬們在推薦深入理解jvm這本書,奈何時間不夠用來部落格上簡單的取取經記錄一下,再此立個flag,明天去圖書館還書的時候一定一定要泡一整天,牆裂建議學校開個通宵自習室!!!我去買咖啡@[email protected] 1、 什麼是JVM?   JVM是Ja