java虛擬機(JVM)堆、棧、方法區的介紹

JVM的基本結構圖:javascript

由圖可知,JVM的內存區域主要能夠劃分爲5塊:java

  1. JVM棧 (Java Virtual Machine Stacks)
  2. 堆內存 (Heap Memory)
  3. 方法區 (Method Area)
  4. 本地方法棧 (Native Method Stacks)
  5. 程序計數器 (Program Counter (PC) Register)

1、JVM棧

  • 程序是在棧內存中運行的,因此棧內存解決的是程序運行時的問題
  • Java以棧幀爲單位保存線程的運行狀態,虛擬機只會對棧執行兩種操做:以棧幀爲單位的壓棧或者出棧
  • 一個線程獨佔一個Java棧(棧裏的數據是線程私有的)
  • 存儲的是基本數據類型和堆中數據的引用(引用地址)
  • 分爲三個部分:基本類型變量區、執行環境上下文、操做指令區
  • 異常:java.lang.StackOverFlowError

2、堆

  • 堆內存解決的是數據存儲的問題
  • 全部線程共享java堆
  • 存儲的是對象和數組(對象自己)
  • 動態的分配內存(運行時分配),生命週期(不肯定)不須要預先告訴編譯器,Java的垃圾回收機制會自動收走不使用的數據
  • 因爲運行時動態分配內存,存儲數據較慢
  • 異常:java.lang.OutOfMemoryError

3、方法區

  • 又稱靜態區
  • 存儲每一個類的信息(包括類名、方法信息、字段信息)、靜態變量、常量以及編譯器編譯後的代碼

4、本地方法棧

和java棧的做用差很少,只不過是爲JVM使用到的native方法(使用非Java語言實現的方法)服務的數組

5、程序計數器

  • 用於保存當前線程執行的內存地址
  • 因爲JVM程序是多線程執行的(線程輪流切換),因此爲了保證線程切換回來後,還能恢復到原先狀態,就須要一個獨立的計數器,記錄以前中斷的地方,可見程序計數器也是線程私有的
  • 注意這個區域是惟一一個不拋出OutOfMemoryError的運行時數據區

下面經過AppMain.java和Sample.java兩塊代碼進一步說明多線程

//運行時, jvm把AppMain的信息都放入方法區
public class AppMain {
    //main 方法自己放入方法區
    public static void main(String[] args) {
        //test1是引用,因此放到棧區裏,Sample是自定義對象應該放到堆裏面
        Sample test1 = new Sample("測試1");
        Sample test2 = new Sample("測試2");
        test1.printName();
        test2.printName();
	}
} 
複製代碼
//運行時, jvm把Sample的信息都放入方法區
public class Sample {
    //new Sample實例後, name 引用放入棧區裏, name 對象放入堆裏
    private String name;      

    /** 構造方法 */
    public Sample(String name) {
        this.name = name;
    }

    /** 輸出 */
    //print方法自己放入方法區裏
    public void printName() {
    	System.out.println(name);
    }
} 
複製代碼
  1. 啓動虛擬機進程,程序從AppMain的開始,先從classpath中找到並讀取AppMain.class二進制文件(編譯後),而後把AppMain類的類信息和方法信息放入方法區,這個過程叫AppMain類的加載過程;jvm

  2. Java虛擬機定位到方法區AppMain類中的main()方法的字節碼,開始執行它的指令,第一條語句是:函數

    Sample test1 = new Sample("測試1");
    複製代碼
  3. 接着Java虛擬機到方法區中查找Sample類的信息,沒有找到,而後經過步驟1從新加載Sample類到方法區;測試

  4. 在堆中爲Sample對象實例分配內存,這個實例持有指向方法區的Sample類的信息的引用(引用是指Sample類的信息在方法區中的內存地址)this

  5. 每個線程都有一個棧,棧裏面的元素被稱爲棧幀,每當線程調用一個方法的時候就會往棧裏壓入一個新幀,這裏的幀是用來存儲方法的參數、局部變量和運算過程當中的臨時數據。位於**「=」前的test1是一個在main()方法中定義的變量,它是一個局部變量,所以,它被會添加到了執行main()方法的主線程的java方法調用棧中,而「=」**將把這個test1變量指向堆區中的Sample實例,也就是說,它持有指向Sample實例的引用spa

  6. 接下來,JAVA虛擬機將繼續執行後續指令,在堆區裏繼續建立另外一個Sample實例,而後依次執行它們的printName()方法。當JAVA虛擬機執行test1.printName()方法時,JAVA虛擬機根據局部變量test1持有的引用,定位到堆區中的Sample實例,再根據Sample實例持有的引用,定位到方法去中Sample類的類型信息,從而得到printName()方法的字節碼,接着執行printName()方法包含的指令.net

6、疑問區

一、Q: Java中的參數傳遞(傳值呢?仍是傳引用?)

A:

程序運行永遠都是在棧中進行的,於是參數傳遞時,只存在傳遞基本類型和對象引用的問題,不會直接傳遞對象自己;

對象傳遞是引用值傳遞,原始類型數據傳遞是值傳遞; 實際上這個傳入函數的值是對象引用的拷貝,即傳遞的是引用的地址值,因此仍是按值傳遞

二、Q: Java對象的大小如何計算?

A:

Object obj = new Object();
複製代碼

這樣在程序中完成了一個java對象的聲明,obj對象所佔的空間爲:

4byte(java棧中保存引用的所須要空間)+ 8byte(java堆中對象所需的空間) = 12byte
複製代碼

全部的java非基本類型的對象都須要默認繼承Object對象,所以不論什麼樣的java對象,其大小都必須是大於8byte

同時java對象大小是8的整數倍,所以obj對象的大小至少爲16byte

7、拓展

對象引用類型分爲強引用軟引用弱引用虛引用

一、強引用:聲明對象時虛擬機生成的引用

Sample sample = new Sample();
複製代碼

sample爲強引用,不會被垃圾回收

二、軟引用:根據系統剩餘內存來決定是否須要回收

換句話說,虛擬機在發生java.lang.OutOfMemoryError時,確定是沒有軟引用存在的

三、弱引用:弱引用與軟引用相似,但在進行垃圾回收時,是必定會被回收掉的

所以其生命週期只存在於一個垃圾回收週期內

四、虛引用虛引用並不會決定對象的生命週期。

若是一個對象僅持有虛引用,那麼它就和沒有任何引用同樣,在任什麼時候候均可能被垃圾回收器回收

虛引用主要用來跟蹤對象被垃圾回收器回收的活動


參考地址

blog.csdn.net/rodbate/art…

相關文章
相關標籤/搜索