天天看點

JVM之深入了解堆空間(一)JVM之深入了解堆5. 圖解對象配置設定過程

JVM之深入了解堆

1. 堆的核心概念

堆針對一個JVM程序來說是唯一的,也就是一個程序隻有一個JVM,但是程序包含多個線程,他們是共享同一堆空間的。
JVM之深入了解堆空間(一)JVM之深入了解堆5. 圖解對象配置設定過程
  1. 一個JVM執行個體隻存在一個堆記憶體,

    堆也是Java記憶體管理的核心區域。

  2. Java堆區在JVM啟動的時候即被建立,其空間大小也就确定了。是JVM管理的最大一塊記憶體空間
  • 堆記憶體的大小是可以調節的。
  • // 如果設定最小堆記憶體和最大堆記憶體,建議設定成相同值
    -Xms10m:最小堆記憶體
    
    -Xmx10m:最大堆記憶體
               
    《Java虛拟機規範》規定,堆可以處于實體上不連續的記憶體空間中,但在邏輯上它應該被視為連續的。
  1. 所有的線程共享Java堆,在這裡

    還可以劃分線程私有的緩沖區(Thread Local Allocation Buffer,TLAB)。

  2. 下圖就是使用:

    Java VisualVM檢視堆空間的内容。

JVM之深入了解堆空間(一)JVM之深入了解堆5. 圖解對象配置設定過程
  1. 《Java虛拟機規範》中對Java堆的描述是:

    所有的對象執行個體以及數組都應當在運作時配置設定在堆上。

  2. 我要說的是:“幾乎”所有的對象執行個體都在這裡配置設定記憶體。—從實際使用角度看的

因為還有一些對象是在棧上配置設定的。
  1. 組和對象可能永遠不會存儲在棧上,因為棧幀中儲存引用,這個引用指向對象或者數組在堆中的位置。
  2. 在方法結束後,堆中的對象不會馬上被移除,僅僅在垃圾收集的時候才會被移除。
    • 也就是觸發了GC的時候,才會進行回收。

    • 如果堆中對象馬上被回收,那麼使用者線程就會收到影響,因為有stop the word。

  3. 堆,是GC(Garbage Collection,垃圾收集器)執行垃圾回收的重點區域。

JVM之深入了解堆空間(一)JVM之深入了解堆5. 圖解對象配置設定過程

2. 堆記憶體細分

現代垃圾收集器大部分都基于

分代收集理論

設計,堆空間細分為:

Java 7 及之前堆記憶體邏輯上分為三部分:

新生代 + 老年代 + 永久代

  • Young Generation Space 新生代 Young/New
    • 又被劃分為Eden區和Survivor區
  • Tenure generation space 老年代 Old/Tenure
  • Permanent Space 永久代 Perm
JVM之深入了解堆空間(一)JVM之深入了解堆5. 圖解對象配置設定過程

Java 8

及之後堆記憶體邏輯上分為三部分:

新生代 + 老年代 + 元空間

  • Young Generation Space 新生代 Young/New
    • 又被劃分為Eden區和Survivor區
  • Tenure generation space 老年代 Old/Tenure
  • Meta Space 元空間 Meta
  • 堆空間内部結構,JDK1.8 之前從永久代 替換成 元空間

JVM之深入了解堆空間(一)JVM之深入了解堆5. 圖解對象配置設定過程

3. 設定堆記憶體大小與OOM

3.1 設定堆記憶體大小

  1. Java堆區用于存儲Java對象執行個體,那麼

    堆的大小在JVM啟動時就已經設定好了

    ,大家可以

    通過選項"-Xmx"和"-Xms"來進行設定。

    • “-Xms"用于表示堆區的起始記憶體,等價于-xx:InitialHeapSize

    • “-Xmx"則用于表示堆區的最大記憶體,等價于-XX:MaxHeapSize

  2. 一旦堆區中的記憶體大小超過“-Xmx"所指定的最大記憶體時,将會抛出OutOfMemoryError異常。

  3. 通常會将

    -Xms

    -Xmx

    兩個參數配置相同的值,其目的是為了能夠在Java垃圾回收機制清理完堆區後不需要重新分隔計算堆區的大小,進而提高性能。
  4. 預設情況下:
    • 初始記憶體大小:實體電腦記憶體大小/64;

    • 最大記憶體大小:實體電腦記憶體大小/4;

  5. 手動設定:-Xms600m -Xmx600m

  • 開發中建議将初始堆記憶體和最大的堆記憶體設定成相同的值。

/**
 * -Xms 用來設定堆空間(年輕代+老年代)的初始記憶體大小
 *  	-X:是jvm運作參數
 *  	ms:memory start
 * -Xmx:用來設定堆空間(年輕代+老年代)的最大記憶體大小
 */

public class HeapSpaceInitial {
    public static void main(String[] args) {
        // 傳回Java虛拟機中的堆記憶體總量
        long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
        // 傳回Java虛拟機試圖使用的最大堆記憶體
        long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;
        System.out.println("-Xms:" + initialMemory + "M");
        System.out.println("-Xmx:" + maxMemory + "M");
    }
}
           
輸出結果
-Xms : 243M
-Xmx : 3607M
           
如何檢視堆記憶體的記憶體配置設定情況?
方法一:
jps  檢視程式的程序号
staat -gc  程序id 檢視堆記憶體的記憶體配置設定情況
           
JVM之深入了解堆空間(一)JVM之深入了解堆5. 圖解對象配置設定過程
方式二:
-XX:+PrintGCDetails
           
JVM之深入了解堆空間(一)JVM之深入了解堆5. 圖解對象配置設定過程

3.2 OutOfMemory舉例

public class OOMTest {
    public static void main(String[] args) {
        ArrayList<Picture> list = new ArrayList<>();
        while(true){
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            list.add(new Picture(new Random().nextInt(1024 * 1024)));
        }
    }
}

class Picture{
    private byte[] pixels;

    public Picture(int length) {
        this.pixels = new byte[length];
    }
}
           
JVM之深入了解堆空間(一)JVM之深入了解堆5. 圖解對象配置設定過程
我們用上面這個個OOM例子,然後設定啟動參數
-Xms600m -Xmx:600m
           
運作後,就出現OOM了,那麼我們可以通過 

VisualVM這個工具

檢視具體是什麼參數造成的OOM。
JVM之深入了解堆空間(一)JVM之深入了解堆5. 圖解對象配置設定過程

4. 年輕代與老年代

  1. 存儲在JVM中的

    Java對象可以被劃分為兩類:

    • 一類是生命周期較短的瞬時對象,這類對象的建立和消亡都非常迅速。
      • 生命周期短的,及時回收即可
    • 另外一類對象的生命周期卻非常長,在某些極端的情況下還能夠與JVM的生命周期保持一緻。
  2. Java堆區進一步細分的話,可以劃分為

    年輕代(YoungGen)和老年代(oldGen)

  3. 其中

    年輕代又可以劃分為Eden空間、Survivor0空間和Survivor1空間(有時也叫做from區、to區)

    • 預設比例
    • Eden:From:to -> 8:1:1

    • 新生代:老年代 - > 1 : 2

JVM之深入了解堆空間(一)JVM之深入了解堆5. 圖解對象配置設定過程

下面這參數開發中一般不會調:

JVM之深入了解堆空間(一)JVM之深入了解堆5. 圖解對象配置設定過程
  1. 配置新生代與老年代在堆結構的占比。
    • 預設-XX:NewRatio=2

      ,表示新生代占1,老年代占2,新生代占整個堆的1/3
    • 可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整個堆的1/5
  2. 當發現在整個項目中,生命周期長的對象偏多,那麼就可以通過調整 老年代的大小,來進行調優。

  3. 在HotSpot中,

    Eden空間和另外兩個survivor空間預設所占的比例是8:1:1

    • 當然開發人員可以通過選項“

      -XX:SurvivorRatio

      ”調整這個空間比例。比如

      -xx:SurvivorRatio=8

  4. 幾乎所有的Java對象都是在Eden區被new出來的。

    絕大部分的Java對象的銷毀都在新生代進行了。

    (有些大的對象在Eden區無法存儲時候,将直接進入老年代)

    • IBM公司的專門研究表明,新生代中80%的對象都是“朝生夕死”的。

  5. 可以使用選項

    "-Xmn"設定新生代最大記憶體大小,這個參數一般使用預設值就可以了。
JVM之深入了解堆空間(一)JVM之深入了解堆5. 圖解對象配置設定過程

5. 圖解對象配置設定過程

5.1 配置設定過程概述

為新對象配置設定記憶體是一件非常嚴謹和複雜的任務,JVM的設計者們不僅需要考慮記憶體如何配置設定、在哪裡配置設定等問題,并且由于記憶體配置設定算法與記憶體回收算法密切相關,是以還需要考慮GC執行完記憶體回收後是否會在記憶體空間中産生記憶體碎片。
  1. new的對象先放伊甸園區。此區有大小限制。

  2. 當伊甸園的空間填滿時,程式又需要建立對象,JVM的垃圾回收器将對伊甸園區進行垃圾回收(

    MinorGC

    ),将伊甸園區中的不再被其他對象所引用的對象進行銷毀。再加載新的對象放到伊甸園區
  3. 然後将伊甸園中的剩餘對象移動到幸存者S0區。
  4. 如果再次觸發垃圾回收,此時上次幸存下來的放到幸存者S0區的,如果沒有回收,就會放到幸存者S1區。
  5. 如果再次經曆垃圾回收,此時會重新放回幸存者S0區,接着再去幸存者S1區。
  6. 啥時候能去老年代呢?可以設定次數。預設是15次。

    • 可以設定參數:

      -XX:MaxTenuringThreshold=N

      進行設定。
  7. 在老年代,相對悠閑。當老年代記憶體不足時,再次觸發GC:

    Major GC

    ,進行老年代的記憶體清理。
  8. 若老年代執行了Major GC之後,發現依然無法進行對象的儲存,就會産生OOM異常。

5.2 圖解過程

我們建立的對象,一般都是存放在Eden區的,當我們Eden區滿了後,就會觸發GC操作,

一般被稱為 YGC / Minor GC操作

JVM之深入了解堆空間(一)JVM之深入了解堆5. 圖解對象配置設定過程
  1. 當我們進行一次垃圾收集後,紅色的将會被回收,而綠色的還會被占用着,存放在S0(Survivor From)區。

    同時我們給每個對象設定了一個年齡計數器,一次回收後就是1。

  2. 同時Eden區繼續存放對象,當Eden區再次存滿的時候,又會觸發一個MinorGC操作,此時GC将會把 Eden和Survivor From中的對象 進行一次收集,

    把存活的對象放到 Survivor To區,同時讓年齡 + 1

JVM之深入了解堆空間(一)JVM之深入了解堆5. 圖解對象配置設定過程

3.

我們繼續不斷的進行對象生成 和 垃圾回收,當Survivor中的對象的年齡達到15的時候,将會觸發一次 Promotion晉升的操作,也就是将年輕代中的對象 晉升到 老年代中。

JVM之深入了解堆空間(一)JVM之深入了解堆5. 圖解對象配置設定過程

幸存區區滿了後?

  1. 特别注意,在Eden區滿了的時候,才會觸發MinorGC,而幸存者區滿了後,不會觸發MinorGC操作。
  2. 如果Survivor區滿了後,将會觸發

    一些特殊的規則

    ,也就是可能直接晉升老年代
    • 舉例:以當兵為例,正常人的晉升可能是 : 新兵 -> 班長 -> 排長 -> 連長
    • 但是也有可能有些人因為做了非常大的貢獻,直接從 新兵 -> 排長

5.3 對象配置設定的特殊情況

JVM之深入了解堆空間(一)JVM之深入了解堆5. 圖解對象配置設定過程

代碼示範對象配置設定過程

我們不斷的建立大對象
/**
 * 代碼示範對象建立過程
 */
public class HeapInstanceTest {
    byte[] buffer = new byte[new Random().nextInt(1024 * 200)];

    public static void main(String[] args) {
        
        ArrayList<HeapInstanceTest> list = new ArrayList<HeapInstanceTest>();
        
        while (true) {
            
            list.add(new HeapInstanceTest());
            
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
    }
}
           
然後設定JVM參數
-Xms600m -Xmx600m
           
然後cmd輸入下面指令,打開VisualVM圖形化界面
jvisualvm
           
然後通過執行上面代碼,通過VisualGC進行動态化檢視

點選下載下傳動态gif

(這個gif有點c大,是以這裡放不下,需要自行下載下傳。。。)

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.atguigu.java1.HeapInstanceTest.<init>(HeapInstanceTest.java:10)
	at com.atguigu.java1.HeapInstanceTest.main(HeapInstanceTest.java:15)
           

5.4 常用的調優工具

JVM之深入了解堆空間(一)JVM之深入了解堆5. 圖解對象配置設定過程

5.5 總結

  1. 針對幸存者s0,s1區的總結:複制之後有交換,誰空誰是to。
  2. 關于垃圾回收:頻繁在新生區收集,很少在老年代收集,幾乎不再永久代和元空間進行收集。
  3. 新生代采用複制算法的目的:是為了減少内碎片。