深入Java虛擬機筆記--JVM內存區域

一,JVM 結構

這裏寫圖片描述

JVM 主要由類加載子系統, 運行時數據區(內存空間),執行引擎,以及本地方法接口等組成。其中運行數據區又包括 方法區,堆,Java棧,本地方法棧,程序計數器。有些區域隨着虛擬機啓動而存在,有些區域依賴用戶線程的啓動而建立,線程的結束而銷燬。

1,類加載子系統 Class Loader

負責加載編譯好的 .class 文件,並裝入內存,使JVM 可以實例化或者以其他方法使用加載後的類。JVM的類加載子系統支持在運行時的動態加載。

類加載子系統 工作原理

類加載分爲裝載、鏈接、初始化三步。

  • 裝載
    通過類的全限定名和ClassLoader加載類,主要是將指定的.class文件加載至JVM。當類被加載以後,在JVM內部就以“類的全限定名+ClassLoader實例ID”來標明類。
    在內存中,ClassLoader實例和類的實例都位於堆中,類的其他信息位於方法區中。
    裝載過程採用了一種“雙親委派模型”的方式。
  • 鏈接
    鏈接的任務是把二進制的類型信息合併到JVM 運行狀態中去。
  • 初始化
    初始化類中的靜態變量,並執行類中的static 代碼,構造函數。
    JVM規範嚴格定義了何時需要對類進行初始化:
    a、通過new關鍵字、反射、clone、反序列化機制實例化對象時
    b、調用類的靜態方法時。
    c、使用類的靜態字段或對其賦值時。
    d、通過反射調用類的方法時。
    e、初始化該類的子類時(初始化子類前其父類必須已經被初始化)。
    f、JVM啓動時被標記爲啓動類的類(簡單理解爲具有main方法的類)。

2,運行數據區(內存)

2.1 **Java 棧**

Java棧是隨線程的啓動而建立,線程的結束而銷燬。棧是線程私有,一個線程對應一個棧,每個棧由許多棧幀組成的.

棧幀

一個方法對應一個棧幀,線程調用某個方法時,虛擬機壓入一個新的棧幀到該線程的Java棧中,當該方法返回時,這個棧幀被彈出並拋棄。

存儲數據

Java棧幀的主要中存儲了方法局部變量,操作數棧,動態連接,以及方法返回地址。
關於棧幀的大小: 編譯期就就已經決定了局部變量表的大小,操作棧數的深度,所以棧幀的大小是固定的,不受程序影響。

這裏寫圖片描述

java棧中的異常

  • StackOverFlowError
    當線程請求的棧深度大於虛擬機所允許的深度時,會拋出 StackOverFlowError。

  • OOM
    虛擬機支持動態擴展時,如果無法申請到足夠的內存,會拋出OOM異常

2.2**本地方法棧**

在Sun JDK中,本地方法棧和Java棧是同一個。

2.3方法區

線程共享的。

存儲數據


方法區存儲類元數據、常量、靜態變量方法區中對於每個類存儲了以下數據:
a.類及其父類的全限定名(java.lang.Object沒有父類)
b.類的類型(Class or Interface)
c.訪問修飾符(public, abstract, final)
d.實現的接口的全限定名的列表
e.常量池
f.字段信息
g.方法信息
h.靜態變量
i.ClassLoader引用
j.Class引用

異常


OOM
當方法區無法滿足內存分配需求時,會拋出OOM異常

Hotspot 將其稱爲永久代(Permanent Generation),但是這部分區域也需要GC。運行時常量池(Runtime Constant Pool)是方法區的一部分,用來存放符號引用,常量,直接引用等。JDK 1.7開始,字符串常量池從方法區中移出

2.4堆 Heap


存儲數據


線程共享的。用於存放對象實例。也被稱爲”GC堆”,因爲堆是垃圾收集器的主要管理區域

異常

在堆內存的管理上,Sun JDK從1.2版本開始引入了分代管理的方式。
分爲年輕代(Young Generation),老年代(Old Generation)。這種分代方式大大改善了垃圾收集的效率。

  • 新生代:每次GC 時,這塊區域的對象都有大批死去,因此稱爲新生代。新生代可以繼續分爲伊甸園(Eden)和兩個存活區(survivor space) 。

  • 老年代:每次GC 時,這塊區域的對象存活率較高。

在這裏看到一個形象的圖片
這裏寫圖片描述
最後堆中的需要等GC 來清理,棧中的直接銷燬了。

3,執行引擎

執行引擎是 JVM 執行Java字節碼的核心。

二,堆,棧,方法區

上面看了JVM的體系結構,現在對比了解JVM 內存中的3個區:堆heap,棧 stack,方法區 method。

堆區:
1. 全部是對象,每個對象都包含與之對應的 class 信息。(class的目的是得到操作指令)。不存放基本類型和數據引用,只存放對象本身.

棧區:
2. Java棧是由許多棧幀組成的。每個線程包含一個棧區。
3. 棧中只保存基礎數據類型的對象和自定義對象的引用(對象都放在堆區)
4. 每個棧中的數據都是私有的,其他棧不能訪問。
5. 棧分爲3 個部分:基本類型變量區,執行環境上下文,操作指令區(存放操作指令)

方法區:
1. 線程共享,方法區包含所有的 class 和 static 變量
2. 方法區中包含的都是在整個程序中永遠唯一的元素,如 class ,static 變量。

爲了更清楚地搞明白髮生在運行時數據區裏的黑幕,我們來準備2個非常簡單的小程序。

public class Demo {  //運行時,JVM 把demo 的信息都放入方法區

    public static void main(String[] args) {   //mian 方法本身放入方法區

        //test1 是引用,所以放到棧區,Sample 是自定義的對象,放到堆中
        Sample s1=new Sample("測試1");
        Sample s2=new Sample("測試2");

        s1.printName();
        s2.printName();
    }
}

 class Sample{    //運行時,JVM 把sample 的信息都放入方法區

     /* 範例名稱 */

    // new  Sample 實例後,name 引用放入到棧區裏,name 對象放入堆裏
    private String name; 

    /* 構造方法 */

    public Sample(String name){
        this.name=name;
    }

    /* 輸出 */
    public void printName(){  // print 方法本身放入方法區裏
        System.out.println(name);
    }
}

發出指令:java Demo ,系統收到指令,啓動一個Java 虛擬機進程,這個進程首先從classpath中找到Demo.class 文件,讀取文件中的二進制數據,然後把Demo 類的類信息存放到運行時數據區。這一過程就是Demo 類的加載過程

接着,Java 虛擬機定位到方法區中 Demo類的Main() 方法,開始執行它的指令:
第一條:Sample test1=new Sample("測試1");

就是讓虛擬機創建一個Sample 實例,並且使用 test1 引用這個實例。貌似小case一樁哦,就讓我們來跟蹤一下Java虛擬機,看看它究竟是怎麼來執行這個任務的:

1、
Java虛擬機一看,不就是建立一個Sample實例嗎,簡單,於是就直奔方法區而去,先找到Sample類的類型信息再說。結果呢,嘿嘿,沒找到@@,這會兒的方法區裏還沒有Sample類呢。可Java虛擬機也不是一根筋的笨蛋,於是,它發揚“自己動手,豐衣足食”的作風,立馬加載了Sample類,把Sample類的類型信息存放在方法區裏。

2、 好啦,資料找到了,下面就開始幹活啦。Java虛擬機做的第一件事情就是在堆區中爲一個新的Sample實例分配內存,
這個Sample實例持有着指向方法區的Sample類的類型信息的引用。這裏所說的引用,實際上指的是Sample類的類型信息在方法區中的內存地址,其實,就是有點類似於C語言裏的指針啦~~,而這個地址呢,就存放了在Sample實例的數據區裏。

3、
在JAVA虛擬機進程中,每個線程都會擁有一個方法調用棧,用來跟蹤線程運行中一系列的方法調用過程,棧中的每一個元素就被稱爲棧幀,每當線程調用一個方法的時候就會向方法棧壓入一個新幀。這裏的幀用來存儲方法的參數、局部變量和運算過程中的臨時數據。OK,原理講完了,就讓我們來繼續我們的跟蹤行動!位於“=”前的Test1是一個在main()方法中定義的變量,可見,它是一個局部變量,因此,它被會添加到了執行* main() 方法的主線程*的 JAVA 方法調用棧中。而“=”將把這個test1變量指向堆區中的Sample實例,也就是說,它持有指向Sample實例的引用。

OK,到這裏爲止呢,JAVA虛擬機就完成了這個簡單語句的執行任務。參考我們的行動向導圖,我們終於初步摸清了JAVA虛擬機的一點點底細了

接下來,JAVA虛擬機將繼續執行後續指令,在堆區裏繼續創建另一個Sample實例,然後依次執行它們的printName()方法。當JAVA虛擬機執行test1.printName()方法時,JAVA虛擬機根據局部變量test1持有的引用,定位到堆區中的Sample實例,再根據Sample實例持有的引用,定位到方法去中Sample類的類型信息,從而獲得printName()方法的字節碼,接着執行printName()方法包含的指令.——-printName 方法在方法區???不太理解(類的所有信息都存儲在方法區中。)

這裏寫圖片描述

參考:淺析Java虛擬機結構與機制
JVM 內存模型
Java(JVM)內存模型
堆(heap)棧(stack)和方法區(method)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章