學習筆記 第1講 程序運行時內存到底是如何進行分配的

學習筆記 第01講:程序運行時,內存到底是如何進行分配的?

拉勾教育:https://kaiwu.lagou.com/course/courseInfo.htm?courseId=67#/detail/pc?id=1855

這一講詳細介紹了jvm運行時的內存如何分佈的,並舉例說明了程序的運行過程。

一、JVM運行時內存數據區

先看一下這張圖:

在這裏插入圖片描述

從這張圖中可以看出,根據數據能否被線程共享,可分爲線程共享數據區線程私有數據區

線程共享數據區包括:

方法區:主要是存儲已經被 JVM 加載的類信息(版本、字段、方法、接口)、常量、靜態變量、即時編譯器編譯後的代碼和數據。是被各個線程共享的內存區域。

:Java堆(Heap)是JVM所管理的內存中最大的一塊,該區域唯一目的就是***存放對象實例,幾乎所有對象的實例都在堆裏面分配***,因此它也是Java垃圾收集器(GC)管理的主要區域,有時候也叫作“GC 堆”(關於堆的 GC 回收機制將會在後續課時中做詳細介紹)。同時它也是所有線程共享的內存區域,因此被分配在此區域的對象如果被多個線程訪問的話,需要考慮線程安全問題。

線程私有數據區包括:

程序計數器:“程序計數器”是虛擬機中一塊較小的內存空間,主要用於記錄當前線程執行的位置。當某一個線程被 CPU 掛起時,需要記錄代碼已經執行到的位置,方便 CPU 重新執行此線程時,知道從哪行指令開始執行。我們熟悉的分支操作、循環操作、跳轉、異常處理等也都需要依賴這個計數器來完成。

注意事項

  1. 在 Java 虛擬機規範中,對程序計數器這一區域沒有規定任何 OutOfMemoryError 情況
  2. 程序計數器是線程私有的,每條線程內部都有一個私有程序計數器。它的生命週期隨着線程的創建而創建,隨着線程的結束而死亡。
  3. 當一個線程正在執行一個 Java 方法的時候,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址。如果正在執行的是 Native 方法,這個計數器值則爲空(Undefined)。

虛擬機棧:虛擬機棧是用來描述 Java 方法執行的內存模型,每個方法被執行的時候,JVM 都會在虛擬機棧中創建一個棧幀,用於存儲***局部變量表、操作數棧、動態連接、返回地址***等信息,每一個方法從調用直到執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到棧的過程。

本地方法棧:本地方法棧和虛擬棧基本相同,只不過是針對本地(native)方法。在開發中如果涉及 JNI 可能接觸本地方法棧多一些,在有些虛擬機的實現中已經將兩個合二爲一了(比如HotSpot)。

二、棧幀的內部結構

棧幀(StackFrame)是用於支持虛擬機進行方法調用和方法執行的數據結構,每一個線程在執行某個方法時,都會爲這個方法創建一個棧幀。我們可以這樣理解:一個線程包含多個棧幀,而每個棧幀內部包含***局部變量表、操作數棧、動態連接、返回地址***等。如下圖所示:
在這裏插入圖片描述

2.1、局部變量表

局部變量表是變量值的存儲空間,我們調用方法時傳遞的參數,以及在方法內部創建的局部變量都保存在局部變量表中。在Java編譯成class文件的時候,就會在方法的 Code 屬性表中的 max_locals 數據項中,確定該方法需要分配的最大局部變量表的容量。

2.2、操作數棧

操作數棧(OperandStack)也常稱爲操作棧,它是一個後入先出棧(LIFO)。同局部變量表一樣,操作數棧的最大深度也在編譯的時候寫入方法的Code屬性表中的max_stacks數據項中,棧中的元素可以是任意Java數據類型,包括long和double。

2.3、動態鏈接

每個棧幀都包含一個執行運行時常量池中該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用過程中的動態連接(Dynamic Linking)。

Class 文件中存放了大量的符號引用,字節碼中的方法調用指令就是以常量池中指向方法的符號引用作爲參數。這些符號引用***一部分會在類加載階段或第一次使用時轉化爲直接引用,這種轉化稱爲靜態解析***。另一部分將在每一次運行期間轉化爲直接引用,這部分稱爲動態連接。

(關於這個概念理解不是很清楚,動態鏈接是地址引用?這個引用是在運行期間產生的?用於區別靜態鏈接?)

2.3、返回地址

當一個方法開始執行以後,只有兩種方法可以退出當前方法:

  1. 當執行遇到返回指令,會將返回值傳遞給上層的方法調用者,這種退出的方式稱爲正常完成出口(Normal Method Invocation Completion),一般來說,調用者的PC計數器可以作爲返回地址。

當執行遇到異常,並且當前方法體內沒有得到處理,就會導致方法退出,此時是沒有返回值的,稱爲異常完成出口(Abrupt Method Invocation Completion),返回地址要通過異常處理器表來確定。

三、StackOverflowError與OutOfMemoryError

3.1、StackOverflowError

每個方法被執行的時候,JVM 都會在虛擬機棧中創建一個棧幀,如果方法的嵌套調用層次太多(如遞歸調用),隨着java棧中的幀的增多,最終導致這個線程的棧中的所有棧幀的大小的總和大於-Xss設置的值,而產生StackOverflowError溢出異常。

示例:

public class StackOverFlow {
    public int stackSize = 0;

    public void stackIncre() {
        stackSize++;
		//遞歸
        stackIncre();
    }

    public static void main(String[] args) throws Throwable{
        StackOverFlow sof = new StackOverFlow();
        try {
            sof.stackIncre();
        } catch (Throwable e) {
            System.out.println(sof.stackSize);
            throw e;
        }
    }
}

3.2、OutOfMemoryError

理論上,虛擬機棧、堆、方法區都有發生OutOfMemoryError的可能。但是實際項目中,大多發生於堆當中。java堆用於存放對象的實例,當需要爲對象的實例分配內存時,而堆的佔用已經達到了設置的最大值(通過-Xmx設置最大值),則拋出OutOfMemoryError異常。

示例:

public class HeapOverFlow {
    public static void main(String[] args) {
        ArrayList<HeapOverFlow> list = new ArrayList<HeapOverFlow>();

        while (true) {
            list.add(new HeapOverFlow());
        }
    }
}

最後上一張總結圖片:
在這裏插入圖片描述




由於水平有限,如果文中存在錯誤之處,請大家批評指正,歡迎大家一起來分享、探討!

博客:http://blog.csdn.net/MingHuang2017

GitHub:https://github.com/MingHuang1024

Email: [email protected]

Huang2017)

GitHub:https://github.com/MingHuang1024

Email: [email protected]

微信:724360018

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