1. 程式人生 > 實用技巧 >JVM 09.1 執行時資料區 堆 核心概述

JVM 09.1 執行時資料區 堆 核心概述

版權宣告:源出處:尚矽谷JVM

部落格來源於大佬整理

一個程序對應一個jvm例項,一個執行時資料區,又包含多個執行緒,這些執行緒共享了方法區和堆,每個執行緒包含了程式計數器、本地方法棧和虛擬機器棧。

核心概述

1.一個jvm例項只存在一個堆記憶體,堆也是java記憶體管理的核心區域

2.Java堆區在JVM啟動的時候即被建立,其空間大小也就確定了。是JVM管理的最大一塊記憶體空間(堆記憶體的大小是可以調節的)

3.《Java虛擬機器規範》規定,堆可以處於物理上不連續的記憶體空間中,但在邏輯上它應該被視為連續的。

4.所有的執行緒共享java堆,在這裡還可以劃分執行緒私有的緩衝區(TLAB:Thread Local Allocation Buffer).(面試問題:堆空間一定是所有執行緒共享的麼?不是,TLAB執行緒在堆中獨有的)

5.《Java虛擬機器規範》中對java堆的描述是:所有的物件例項以及陣列都應當在執行時分配在堆上。從實際使用的角度看,“幾乎”所有的物件的例項都在這裡分配記憶體 (‘幾乎’是因為可能儲存在棧上,另見逃逸分析)

6。陣列或物件永遠不會儲存在棧上,因為棧幀中儲存引用,這個引用指向物件或者陣列在堆中的位置。

7.在方法結束後,堆中的物件不會馬上被移除,僅僅在垃圾收集的時候才會被移除

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

配置堆記憶體及檢視jvm程序

編寫HeapDemo/HeapDemo1程式碼:

public class HeapDemo {
    public static void main(String[] args) {
        System.out.println("start...");
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("end...");
    }
}

首先對虛擬機器進行配置,如圖 Run-Edit configurations:

在jdk目錄,jdk1.8.0_171.jdk/Contents/Home/bin下找到jvisualvm 執行(或者直接終端執行jvisualvm),檢視程序,可以看到我們設定的配置資訊:

可以看到HeapDemo配置-Xms10m, 分配的10m被分配給了新生代3m和老年代7m:

分析SimpleHeap的jvm情況

public class SimpleHeap {
    private int id;//屬性、成員變數

    public SimpleHeap(int id) {
        this.id = id;
    }

    public void show() {
        System.out.println("My ID is " + id);
    }
    public static void main(String[] args) {
        SimpleHeap sl = new SimpleHeap(1);
        SimpleHeap s2 = new SimpleHeap(2);

        int[] arr = new int[10];

        Object[] arr1 = new Object[10];
    }
}

堆的細分記憶體結構

JDK 7以前: 邏輯上分為新生區+養老區+永久區(即Xms/Xmx分配的記憶體物理上沒有涉及永久區)

  • Young Generation Space:又被分為Eden區和Survior區
  • Tenure generation Space: ==Old/Tenure==
  • Permanent Space: ==Perm==

JDK 8以後: 邏輯上分為新生區+養老區+元空間(即Xms/Xmx分配的記憶體物理上沒有涉及元空間)
  • Young Generation Space:又被分為Eden區和Survior區
  • Tenure generation Space: ==Old/Tenure==
  • Meta Space: ==Meta==

設定堆記憶體大小與OOM

1.Java堆區用於儲存java物件例項,堆的大小在jvm啟動時就已經設定好了,可以通過 "-Xmx"和 "-Xms"來進行設定

  • -Xms 用來設定堆空間(年輕代+老年代)的初始記憶體大小,等價於 -XX:InitialHeapSize
    • -X 是jvm的執行引數
    • ms 是memory start
  • -Xmx 用於設定堆的最大記憶體,等價於 -XX:MaxHeapSize

2.一旦堆區中的記憶體大小超過 -Xmx所指定的最大記憶體時,將會丟擲OOM異常。

  • 預設情況下,初始記憶體大小:實體記憶體大小/64;最大記憶體大小:實體記憶體大小/4。
  • 手動設定:-Xms600m -Xmx600m

3.通常會將-Xms和-Xmx兩個引數配置相同的值,其目的就是為了能夠在java垃圾回收機制清理完堆區後不需要重新分隔計算堆區的大小,從而提高效能。

  • 比如說:預設空餘堆記憶體小於40%時,JVM就會增大堆直到-Xmx的最大限制;空餘堆記憶體大於70%時,JVM會減少堆直到 -Xms的最小限制。
    因此伺服器一般設定-Xms、-Xmx相等以避免在每次GC 後調整堆的大小

4.檢視設定的堆記憶體引數:

  • 方式一: ==終端輸入jps== , 然後 ==jstat -gc 程序id==
  • 方式二:(控制檯列印)Edit Configurations->VM Options 新增 ==-XX:+PrintGCDetails==

檢視堆記憶體大小測試程式碼

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");//-Xms : 245M
        System.out.println("-Xmx : " + maxMemory + "M");//-Xmx : 3641M

        System.out.println("系統記憶體大小為:" + initialMemory * 64.0 / 1024 + "G");//系統記憶體大小為:15.3125G
        System.out.println("系統記憶體大小為:" + maxMemory * 4.0 / 1024 + "G");//系統記憶體大小為:14.22265625G

        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

堆大小分析

設定堆大小為600m,打印出的結果為575m,這是因為倖存者區S0和S1各佔據了25m,但是他們始終有一個是空的,存放物件的是伊甸園區和一個倖存者區。

OOM示例

java.lang.OutOfMemoryError: Java heap space。程式碼示例:

/**
 * -Xms600m -Xmx600m
 */
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];
    }
}