11.JDK8内存模型、本地方法栈、虚拟机栈、栈帧结构(局部变量表、操作数栈、方法出口、虚拟机栈与本地方法栈的关系、寄存器、方法区、堆(Heap)、jvm中的常量池、Metaspace(元空间))
11.JDK8內存模型
11.1.本地方法棧(Native Method Stacks)
11.2.虛擬機棧(Java Virtual Machine Stacks)
11.3.棧幀結構
11.3.1.局部變量表
11.3.2.操作數棧
11.3.3.方法出口
11.4.虛擬機棧與本地方法棧的關系
11.5.寄存器(The pc Register)
11.6.方法區(Method Area)
11.7.堆(Heap)
11.8.jvm中的常量池
11.9.Metaspace(元空間)
11.10.堆內存劃分
11.JDK8內存模型
本文轉自:https://www.jianshu.com/p/44df41ebdbf6
通常談到JVM的內存模型,一般人會想到堆和棧等,那么堆和棧如何理解呢?
棧是運行時的單位
堆是存儲的單位
通俗來說棧解決的是程序如何和運行,數據如何處理的問題;而堆解決的是數據如何存儲,存儲在哪兒的問題。
如上圖所示,java虛擬機內存模型主要分為以上五個部分,這里以jdk8為學習對象。
11.1.本地方法棧(Native Method Stacks)
本地方法棧(Native Method Stacks)與虛擬機棧所發揮的作用是非常相似的。其區別在于虛擬機棧為虛擬機執行Java方法所服務,而本地方法棧則是為虛擬機使用到的native方法所服務。
本地方法棧也是一個私有(線程私有)的內存區域,也是后進先出。
虛擬機可以自由實現它,有的虛擬機(如HotSpot虛擬機)直接就把本地方法棧和虛擬機棧合二為一。
本地方法棧區域也會拋出StackOverflowError和OutOfMemoryError異常
11.2.虛擬機棧(Java Virtual Machine Stacks)
每個Java線程都有一個私有Java虛擬機棧,與該線程同時創建。
在虛擬機棧內,每個方法會生成一個棧幀。每個棧幀代表一次次的方法調用,一個方法的執行到執行完成的過程,代表棧幀從入棧到出棧的過程。
虛擬機棧會拋出StackOverflowError和OutOfMemoryError。
11.3.棧幀結構
下圖表示了棧幀的組成結構:
11.3.1.局部變量表
局部變量表是一組變量值存儲空間,用于存放方法參數和方法內部定義的局部變量。
11.3.2.操作數棧
操作數棧是一個后入先出的棧。
一個方法剛開始執行時操作數棧是空的,方法執行過程中會有各種字節碼指令往操作數棧中寫入和提取內容,也就是出棧 / 入棧操作。
例如執行iadd指令時,就會將最接近棧頂的兩個int元素取出并相加,然后將相加的結果再入棧。
操作數棧中元素的數據類型必須與字節碼指令的序列嚴格匹配,在編譯程序代碼的時候,編譯器要嚴格保證這一點。比如剛才的iadd指令,它取出的元素必須是int的,不能出現諸如long和float類型的變量。
雖然概念模型中不同棧幀之間是完全相互獨立的,但大多虛擬機實現中會有一些優化處理:令兩個棧幀出現一部分重疊,讓下面的棧幀的部分操作數棧與上面棧幀的部分局部變量表重疊在一起重疊在一起,這樣在進行方法調用時就可以公用一部分數據,無須進行額外的參數復制傳遞,如圖所示:
每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支持方法調用過程中的動態連接。
靜態解析:我們知道Class文件的常量池中存有大量的符號引用,字節碼中的方法調用指令就以常量池中指向方法的符號引用作為參數,這些符號引用一部分會在類加載階段或者第一次使用的時候就轉化為直接引用,這種轉化稱為靜態解析。
動態鏈接:除去靜態解析的另外一部分將在每一次運行期間轉化為直接引用,這部分稱為動態鏈接。
所以要執行某個方法時,某個指令(例如invokevirtual)將常量池中的引用作為參數,而根據這個引用就可以找到真正的棧幀。
關于方法的解析與調用,參考:https://blog.csdn.net/reachwang/article/details/103058653
11.3.3.方法出口
方法出口也可以通俗的理解為方法返回方式:在jvm中,方法返回方式有兩種:正常和異常
正常出口:當程序執行遇到方法返回的字節碼指令,就完成此次方法執行,并根據調用方指定的返回值返回(可以無返回值)。
異常出口:方法在執行中遇到了異常,并且在方法體內沒有得到處理,會導致方法退出,這時候不會有任何返回值給調用方。
程序正常退出時,相當于把當前棧幀出棧,調用pc計數器的值作為返回地址,即調用該方法的指令的下一條指令的地址。
程序異常退出時:當程序發生異常時,返回地址需要通過異常表來確定,在棧幀中沒有保存異常表。
11.4.虛擬機棧與本地方法棧的關系
為了更好地理解虛擬機棧和本地方法棧的結構模型以及關系,我們以網上的例子簡單描述下,如下圖:
11.5.寄存器(The pc Register)
Java虛擬機可以支持多個線程同時執行,每個Java線程都有其自己的 pc(程序計數器)寄存器。在任何時候,每個Java虛擬機線程都在執行單個方法的代碼,即該線程的當前方法。(如果不是native,則該pc寄存器包含當前正在執行的Java虛擬機指令的地址。如果線程當前正在執行的方法是native,則Java虛擬機的pc寄存器值未定義。
pc寄存器中的值就是當前指令所在的內存地址,即returnAddress類型的數據,當線程執行native方法時,pc中的值為undefined。
11.6.方法區(Method Area)
Java虛擬機具有一個在所有Java虛擬機線程之間共享的方法區域。該方法區域類似于常規語言的編譯代碼的存儲區域,或者類似于操作系統過程中的“文本”段。它存儲每個類的結構,例如運行時常量池,字段和方法數據,以及方法和構造函數的代碼,包括用于類和實例初始化以及接口初始化的特殊方法。
方法區是在虛擬機啟動時創建的。盡管方法區在邏輯上是堆的一部分,但是可以選擇不進行垃圾回收或壓縮。該規范沒有規定方法區域的位置或用于管理已編譯代碼的策略。方法區域可以是固定大小的,或者可以根據計算的需要進行擴展,如果不需要更大的方法區域,則可以縮小。方法區域的內存不必是連續的。
可能拋出OutOfMemoryError異常。
11.7.五、堆(Heap)
Java虛擬機具有一個在所有Java虛擬機線程之間共享的堆。堆是運行時數據區,從中分配所有類實例和數組的內存。
堆是在虛擬機啟動時創建的。對象的堆存儲由GC(垃圾收集器)回收;對象永遠不會顯式釋放。Java虛擬機可以根據實現者的系統要求選擇GC。堆的大小可以是固定的,也可以根據計算要求進行擴展,如果不需要更大的堆,則可以將其收縮。堆的內存不必是連續的。
可能拋出OutOfMemoryError異常。
在jdk1.8之前的版本對內存空間是不同的,主要區別在于:1.8中刪除了永久代,新增了元空間。
元空間的本質和永久代類似,都是對JVM規范中方法區的實現。不過元空間與永久代之間最大的區別在于:元空間并不在虛擬機中,而是使用本地內存。因此,默認情況下,元空間的大小僅受本地內存限制,但可以通過參數來指定元空間的大小。
11.8.jvm中的常量池
參考文章:https://blog.csdn.net/weixin_40999907/article/details/87907083
方法區:運行時常量池
Class文件:常量池
堆:String常量池
11.9.Metaspace(元空間)
本部分轉自:https://blog.csdn.net/universe_ant/article/details/58585854
其實,移除永久代的工作從JDK 1.7就開始了。JDK 1.7中,存儲在永久代的部分數據就已經轉移到Java Heap或者Native Heap。但永久代仍存在于JDK 1.7中,并沒有完全移除,譬如符號引用(Symbols)轉移到了native heap;字面量(interned strings)轉移到了Java heap;類的靜態變量(class statics)轉移到了Java heap。我們可以通過一段程序來比較JDK 1.6、JDK 1.7與JDK 1.8的區別,以字符串常量為例:
package com.paddx.test.memory;import java.util.ArrayList; import java.util.List;public class StringOomMock {static String base = "string";public static void main(String[] args) {List<String> list = new ArrayList<String>();for(int i = 0; i < Integer.MAX_VALUE; i++) {String str = base + base;base = str;list.add(str.intern());}}}JDK1.6的運行結果:
JDK1.7的運行結果:
JDK 1.8的運行結果:
從上述結果可以看出,JDK 1.6下,會出現“PermGen space”的內存溢出,而在JDK 1.7和JDK 1.8中,會出現堆內存溢出,并且JDK 1.8中參數PermSize和MaxPermSize已經失效。因此,可以大致驗證JDK 1.7和JDK 1.8中將字符串常量由永久代轉移到堆中,并且JDK 1.8中已經不存在永久代的結論。現在我們來看一看元空間到底是一個什么東西?
JDK1.8對JVM架構的改造將類元數據放到本地內存中,另外,將常量池和靜態變量放到Java堆里。HotSpot VM將會為類的元數據明確分配和釋放本地內存。在這種架構下,類元信息就突破了原來-XX:MaxPermSize的限制,現在可以使用更多的本地內存。這樣就從一定程度上解決了原來在運行時生成大量類造成經常Full GC問題,如運行時使用反射、代理等。所以升級以后Java堆空間可能會增加。
元空間的本質和永久代類似,都是對JVM規范中方法區的實現。不過元空間與永久代之間的最大區別在于:元空間并不在虛擬機中,而是使用本地內存。因此,默認情況下,元空間的大小僅受本地內存限制,但可以通過以下參數指定元空間的大小:
-XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對改值進行調整:
-XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對改值進行調整:如果釋放了大量的空間,就適當降低該值;如果釋放了很少的空間,那么在不超過MaxMetaspaceSize時,適當提高該值。-XX:MaxMetaspaceSize,最大空間,默認是沒有限制的。除了上面的兩個指定大小的選項外,還有兩個與GC相關的屬性:
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空間容量的百分比,減少為分配空間導致的垃圾收集器。-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空間容量的百分比,減少為釋放空間所導致的垃圾收集。現在我們在JDK 1.8重新運行一下上面第二部分(PermGen(永久代))的代碼,不過這次不再指定PermSize和MaxPermSize。而是制定MetaspaceSize和MaxMetaspaceSize的大小。輸出結果如下:
11.10.堆內存劃分
以下轉自:https://www.cnblogs.com/cjsblog/p/9850300.html
在JDK1.7以及其前期的JDK版本中,堆內存通常被分為三塊區域:Young Generation、Old Generation、Permanent Generation for VM Matedata。
在JDK1.8中把存放元數據中的永久內存從堆內存中移到了本地內存中,JDK1.8中JVM堆內存結構就變成了如下:
推統計信息
總結
以上是生活随笔為你收集整理的11.JDK8内存模型、本地方法栈、虚拟机栈、栈帧结构(局部变量表、操作数栈、方法出口、虚拟机栈与本地方法栈的关系、寄存器、方法区、堆(Heap)、jvm中的常量池、Metaspace(元空间))的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 社会保障卡有什么用 这种功能很少有人使
- 下一篇: 13.2.虚拟化工具--jstat