我個人對JVM內存模型的理解

先上一張圖,我們看下JVM內存的大概模型:



方法區:Permanent Generation:(PermGen)
1存儲:靜態類型數據:Java Class、Method、Field、Constant數據(與垃圾收集器要收集的Java對象關係不大)
2溢出:如果以上數據過多,就會導致
3進入:Class Load之後


堆區:伊甸區(Eden),倖存者區域(Survivor Sapce),老年代(Old Generation Space)

伊甸區+倖存者區 = 新生代 變量首先在伊甸區,垃圾回收後存留的進入倖存者區,當Survivor Space空間滿了後進入老年代。每次GC後,Eden內存塊會被清空

1存儲:動態類型數據:通過new 方式構建的對象實例
2溢出:動態加載了大量Java類而導致溢出
3進入:new 或者其他類似方式創建的對象

       

本地區:線程區

1存儲:動態類型數據:程序計數器PC、執行堆棧信息、對堆區變量的引用、方法的形參
2溢出:如果以上數據過多,就會導致

3進入:啓動一個主線程或者子線程會開闢


下面具體展開:


本地區:

1、Program Counter Register(程序計數器):

一塊較小的內存空間, 作用是當前線程所執行字節碼的行號指示器(類似於傳統CPU模型中的PC), PC在每次指令執行後自增, 維護下一個將要執行指令的地址. 在JVM模型中, 字節碼解釋器就是通過改變PC值來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴PC完成(僅限於Java方法, Native方法該計數器值爲undefined). 

不同於OS以進程爲單位調度, JVM中的併發是通過線程切換並分配時間片執行來實現的. 在任何一個時刻, 一個處理器內核只會執行一條線程中的指令. 因此, 爲了線程切換後能恢復到正確的執行位置, 每條線程都需要有一個獨立的程序計數器, 這類內存被稱爲“線程私有”內存.

2、Java Stack(虛擬機棧)

虛擬機棧描述的是Java方法執行的內存模型: 每個方法被執行時會創建一個棧幀(Stack Frame)用於存儲局部變量表操作數棧動態鏈接方法出口等信息. 每個方法被調用至返回的過程, 就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程(VM提供了-Xss來指定線程的最大棧空間, 該參數也直接決定了函數調用的最大深度).

  • 局部變量表(對應我們常說的‘堆棧’中的‘棧’)存放了編譯期可知的各種基本數據類型(如boolean、int、double等) 、對象引用(reference : 不等同於對象本身, 可能是一個指向對象起始地址的指針, 也可能指向一個代表對象的句柄或其他與此對象相關的位置, 見下: HotSpot對象定位方式) 和 returnAddress類型(指向一條字節碼指令的地址). 其中longdouble佔用2個局部變量空間(Slot), 其餘只佔用1個. 如下Java方法代碼可以使用javap命令或javassist等字節碼工具讀到:
public String test(int a, long b, float c, double d, Date date, List<String> list) {
    StringBuilder sb = new StringBuilder().append(a).append(b).append(c).append(d).append(date);

    for (String str : list) {
        sb.append(str);
    }

    return sb.toString();
}

注: javap/javassist讀到的其實是靜態數據, 而局部變量表內存儲的卻是運行時動態加載的動態數據, 但因爲局部變量表所需的內存空間在編譯期間即可完成分配, 當進入一個方法時, 這個方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運行期間大小不會改變, 因此可以在概念上認定這兩部分內容存儲的數據格式相同.

3、Native Method Stack(本地方法棧)

Java Stack作用類似, 區別是Java Stack爲執行Java方法服務, 而本地方法棧則爲Native方法服務, 如果一個VM實現使用C-linkage模型來支持Native調用, 那麼該棧將會是一個C棧(詳見: JVM學習筆記-本地方法棧(Native Method Stacks)), 但HotSpot VM直接就把本地方法棧和虛擬機棧合二爲一.


方法區:

即我們常說的永久代(Permanent Generation), 用於存儲被JVM加載的類信息常量靜態變量即時編譯器編譯後的代碼等數據. HotSpot VM把GC分代收集擴展至方法區, 即使用Java堆的永久代來實現方法區, 這樣HotSpot的垃圾收集器就可以像管理Java堆一樣管理這部分內存, 而不必爲方法區開發專門的內存管理器(永久帶的內存回收的主要目標是針對常量池的回收類型的卸載, 因此收益一般很小)

  • 運行時常量池 
    方法區的一部分. Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項常量池(Constant Pool Table)用於存放編譯期生成的各種字面量和符號引用, 這部分內容會存放到方法區的運行時常量池中(如前面從test方法中讀到的signature信息). 但Java語言並不要求常量一定只能在編譯期產生, 即並非預置入Class文件中常量池的內容才能進入方法區運行時常量池, 運行期間也可能將新的常量放入池中, 如Stringintern()方法.

堆區:

幾乎所有對象實例和數組都要在堆上分配(棧上分配、標量替換除外), 因此是VM管理的最大一塊內存, 也是垃圾收集器的主要活動區域. 由於現代VM採用分代收集算法, 因此Java堆從GC的角度還可以細分爲: 新生代(Eden區From Survivor區To Survivor區)和老年代; 而從內存分配的角度來看, 線程共享的Java堆還還可以劃分出多個線程私有的分配緩衝區(TLAB). 而進一步劃分的目的是爲了更好地回收內存和更快地分配內存.


總結:


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