JVM --- 堆&棧&堆參數調優 一. 方法區: 二. 棧: 三. 堆: 四. JVM調優

一. 方法區:

線程共享的運行時內存區域,它存儲了每一個類的結構信息。什麼叫類的結構信息,其實就是上一篇講類加載器時說的類的模板。也就是類的屬性、構造器、方法、常量池等。而且,方法區是一種規範,不是具體實現java7及以前的實現叫永久代java8開始,方法區的實現叫元空間。

二. 棧:

1. 棧的基本介紹:
棧也叫棧內存,主要管java程序的運行,是線程私有的。它的生命週期是跟隨線程的生命週期的,線程創建時創建,線程結束棧內存就釋放。棧不存在垃圾回收。8種基本類型的變量、對象的引用變量和實例方法都是在函數的棧內存中分配的。

棧幀主要保存以下3類數據(棧幀就是方法,在java代碼中它叫方法,壓到棧裏面就叫棧幀):

  • 本地變量:即輸入參數、輸出參數和方法內的變量;

  • 棧操作:記錄出棧、入棧的操作;

  • 棧幀數據:類文件、方法等;

當你在main方法中調用另一個方法fun的時候,首先是main方法進棧,壓到棧底,然後是fun方法進棧,等fun執行完,會自動將fun彈出,再繼續執行main,最後main方法出棧。如果fun是一個沒有終止條件的遞歸方法,那麼就會不停地有fun入棧,直到棧裝不下。此時會拋出一個錯誤:Exception in thread "main" java.lang.StackOverflowError。這就是棧內存溢出,注意,這是一個error,而不是exception。

2. 棧、堆、方法區的交互:

Person p1 = new Person();
Person p2 = new Person();

p1、p2是引用,上面說了,引用是棧中的,new Person()是在堆中完成的,Person的模板是存在方法區的,也就是,堆中new對象的時候,用的是方法區中的模板,所以能保證new出來的兩個Person實例結構都是一樣的。所以棧中的p1、p2存儲的是實例在堆中地址值。

三. 堆:

1. 堆基本介紹:

一個JVM實例只存在一個堆,堆的內存大小可以調節,存放的是new出來的實例和數組。堆內存邏輯上分爲三部分:

  • 新生區(新生代):佔1/3的堆空間,又包括伊甸區倖存0區(S0區,from區)倖存1區(S1區,to區),這三個區的內存比例爲:伊甸區 : S0區 : S1區 = 8 : 1 : 1,而且,from區和to區不是固定的,誰空誰是to;

  • 養老區(老年代):佔2/3的堆空間;

  • 永久區(永久代)/元空間:在java7中叫永久區,java8換成了元空間,永久代是使用JVM的堆內存,而元空間是使用本機的物理內存。存放的是JDK自帶的class、interface等元數據,此區域不會進行垃圾回收,關閉JVM纔會釋放此區域內存。

永久區/元空間是邏輯上的劃分,所以物理上堆內存就是新生區 + 養老區

2. 新生區、養老區以及GC介紹:

new出來的對象首先是在伊甸區,伊甸園嘛,生命初始的地方。伊甸區對象滿了的話,就會觸發輕GC,即YGC。觸發YGC後,倖存者將會被複制到from區,伊甸區域會被清空。當伊甸區第二次觸發YGC,就會掃描伊甸區和from區,對這兩個區進行垃圾回收,經過這兩次垃圾回收還存活的對象,就會被複制到to區,伊甸區和form區就會被清空,並且會把這些對象的年齡加一,當年齡達到閾值(默認是15,可通過-XX:MaxTenuringThreshold配置)時,這些對象就會被複制到養老區。此時,原先的form區是空的,原先的to區存放了歷經兩次YGC還存活的對象。上面說了,誰空誰就是to區,所以原先的from區現在變成了to區。這就是YGC的三個過程:複製 ---> 清空 ---> 互換

如果養老區也滿了,就會在養老區觸發full GC,如果多次full GC還是沒能騰出空間來,就會內存溢出,即OOM異常。

四. JVM調優

1. 基本介紹:

JVM調優,其實就是堆參數的調整。

先說一下這張圖,Minor GC就是上面說的YGC,Major GC就是full GC,S0和S1區有雙向箭頭,表示它們不是固定的,誰空誰就是to區(S1區)。

常見堆參數:

  • -Xms:堆內存(新生區+養老區)的初始大小,默認爲物理內存的1/64
  • -Xmx:堆內存(新生區+養老區)的最大值,默認爲物理內存的1/4
  • -Xmn:新生區的大小
  • -XX:PermSize:永久代的初始值(jdk1.7)
  • -XX:MaxPermSize:永久代的最大值(jdk1.7)
  • -XX:MaxTenuringThreshold:設置對象在新生代中存活的次數,默認是15

2. 堆內存調優簡介:

上面說了xms和xmx的默認大小,怎麼證明呢?用下面的代碼可以證明:

public static void main(String[] args) {
    System.out.println(Runtime.getRuntime().availableProcessors()); // cpu核數
    double xms = Runtime.getRuntime().totalMemory() / 1024 / 1024; // xms
    double xmx = Runtime.getRuntime().maxMemory() / 1024 / 1024; // xmx
    System.out.println("xms:" + xms + "MB");
    System.out.println("xmx:" + xmx + "MB");
}

16G的筆記本,打印出來的xms大概是240MB,xmx大概是3600MB。爲什麼打印出來的更小?因爲16G內存的筆記本,實際可用的內存是不到16G的。

xms和xmx,雖然一個是初始值一個最大值,但是,生產上這兩個值一定要一樣,爲的是避免GC程序和應用程序爭搶內存,導致可用內存忽高忽低;

怎麼配置這兩個值呢?eclipse和idea中,點擊run configuration,可以配置VM arguments,將下面這串配置進去,就可以配置xms和xmx的大小,以及打印堆的信息:

-Xms1024M -Xmx1024M -XX:+PrintGCDetails

這裏將xms和xmx都配置了1024,並且打印了堆信息。配置了這些再次運行上面那段代碼,就會打印出如下信息(jdk1.8):

從打印出來的信息可以發現,xms和xmx的配置生效了。從堆信息可以發現,堆確實上述由新生區、養老區和元空間構成,而且,新生區305664k加上養老區的699392k剛好等於981M,也說明了物理上堆只分爲新生區和養老區,元空間是邏輯上的存在。

3. OOM異常:

上面說了,如果堆內存被佔用滿了,就會出現OOM異常。但是默認情況下xmx是內存的1/4,不容易出現這個異常。上面又說了配置這兩個參數的方法,所以,我們可以將xms和xmx都配製成10M,然後再執行下面這段代碼,就很容易出現OOM了。

String str = "hello";
while (true) {
    str += str + new Random().nextInt(88888888) + new Random().nextInt(99999999);
}

string類型用加號拼接,其實是new一個string然後用append方法的,所以這裏會一直new對象,並且用了隨機數,所以基本上都不重複的對象,不會從常量池裏拿到。執行這段代碼後,程序報瞭如下的錯誤:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOfRange(Arrays.java:3664)
    at java.lang.String.<init>(String.java:207)
    at java.lang.StringBuilder.toString(StringBuilder.java:407)

這就是OOM異常了,並且中報錯之前,打印了GC相關信息,如下:

[GC (Allocation Failure) [PSYoungGen: 1855K->491K(2560K)] 1855K->991K(9728K), 0.0024182 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 2168K->272K(2560K)] 2668K->1753K(9728K), 0.0022543 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 1621K->272K(2560K)] 7032K->5682K(9728K), 0.0009517 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 272K->272K(2560K)] 5682K->5682K(9728K), 0.0007530 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 272K->0K(2560K)] [ParOldGen: 5410K->3170K(7168K)] 5682K->3170K(9728K), [Metaspace: 2752K->2752K(1056768K)], 0.0076104 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 40K->32K(2560K)] 5829K->5821K(9728K), 0.0007024 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 32K->32K(1536K)] 5821K->5821K(8704K), 0.0005743 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 32K->0K(1536K)] [ParOldGen: 5789K->4479K(7168K)] 5821K->4479K(8704K), [Metaspace: 2752K->2752K(1056768K)], 0.0062029 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 20K->32K(2048K)] 7118K->7130K(9216K), 0.0007837 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 32K->0K(2048K)] [ParOldGen: 7098K->3170K(7168K)] 7130K->3170K(9216K), [Metaspace: 2752K->2752K(1056768K)], 0.0054553 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 20K->0K(2048K)] 5809K->5789K(9216K), 0.0003817 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 5789K->5789K(9216K), 0.0003808 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 5789K->5789K(7168K)] 5789K->5789K(9216K), [Metaspace: 2752K->2752K(1056768K)], 0.0040374 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 5789K->5789K(9216K), 0.0003072 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 5789K->5775K(7168K)] 5789K->5775K(9216K), [Metaspace: 2752K->2752K(1056768K)], 0.0075411 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 

可以看到,這裏先進行GC,然後是full GC,full GC也搞不定了,就報錯了,這與上面講的是一樣的。那麼打印的GC信息是什麼意思呢?拿出一條來分析一下:

[
  GC (Allocation Failure)
  [PSYoungGen: 1855K->491K(2560K)]
  1855K->991K(9728K), 0.0011226 secs
] 
[
  Times: user=0.00 sys=0.00, real=0.00 secs
] 

我們一行一行的看,意思分別是:

表示由於分配失敗發生GC;
GC發生在新生區(總內存2560K),GC之前該區用了1855K,GC之後用了491K;
堆內存總共9728K,GC之前堆內存佔用1855K,GC之後佔用991K,本次GC總共耗時0.0011226秒;
最後一行是GC時用戶耗時、系統耗時和實際耗時

full GC的也是一樣的:

[
  Full GC (Allocation Failure) 
  [PSYoungGen: 0K->0K(2048K)] 
  [ParOldGen: 5789K->5775K(7168K)] 5789K->5775K(9216K), 
  [Metaspace: 2752K->2752K(1056768K)], 0.0075411 secs
] 
[Times: user=0.01 sys=0.00, real=0.01 secs] 

這裏的意思分別是:

表示由於分配失敗發生full GC;
full GC前新生區佔用0k,full GC後佔用0k,該區總內存2048k;
full GC之前老年區佔用5789k,之後佔用5775k,該區總大小7168k,full GC之前堆內存佔用5789k,之後佔用5575k,堆內存總大小9216k;
full GC前元空間佔用2752k,之後佔用2752k,元空間總大小1056768k,full GC耗時0.0075411秒;
最後一行是GC時用戶耗時、系統耗時和實際耗時
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章