參考:https://blog.csdn.net/reachwang/article/details/99755178
通常談到JVM的內(nèi)存模型述寡,一般人會想到堆和棧等肿轨,那么堆和棧如何理解呢?
棧是運行時的單位;
堆是存儲的單位。
通俗來說棧解決的是程序如何運行,數(shù)據(jù)如何處理的問題;而堆解決的是數(shù)據(jù)如何存儲,存儲在哪的問題仅财。
如上圖所示,java虛擬機內(nèi)存模型主要分為以上五個部分碗淌,這里以java8為學習對象盏求。
一、本地方法棧 (Native Method Stacks)
本地方法棧(Native Method Stacks)與虛擬機棧所發(fā)揮的作用是非常相似的亿眠。其區(qū)別在于虛擬機棧為虛擬機執(zhí)行Java方法所服務碎罚,而本地方法棧則是為虛擬機使用到的native方法所服務。
本地方法棧也是一個私有(線程私有)的內(nèi)存區(qū)域纳像,也是后進先出荆烈。
虛擬機可以自由實現(xiàn)它,有的虛擬機(如HotSpot虛擬機)直接就把本地方法棧和虛擬機棧合二為一竟趾。
本地方法棧區(qū)域也會拋出StackOverflowError和OutOfMemoryError異常:
二憔购、虛擬機棧 (Java Virtual Machine Stacks)
每個Java線程都有一個私有Java虛擬機棧,與該線程同時創(chuàng)建岔帽。
在虛擬機棧內(nèi)玫鸟,每個方法會生成一個棧幀。每個棧幀代表一次次的方法調(diào)用山卦,一個方法的執(zhí)行到執(zhí)行完成的過程鞋邑,代表棧幀從入棧到出棧的過程诵次。
虛擬機棧會拋出StackOverflowError和OutOfMemoryError账蓉。
2.1 棧幀結(jié)構(gòu)
下圖表示了棧幀的組成結(jié)構(gòu):
2.1.1 局部變量表
局部變量表是一組變量值存儲空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量逾一。
具體數(shù)據(jù)類型的內(nèi)容參考:http://www.reibang.com/p/f46e02173552
2.1.2 操作數(shù)棧
操作數(shù)棧是一個后入先出的棧铸本。
一個方法剛開始執(zhí)行時操作數(shù)棧是空的,方法執(zhí)行過程中會有各種字節(jié)碼指令往操作數(shù)棧中寫入和提取內(nèi)容遵堵,也就是出棧 / 入棧操作箱玷。
例如執(zhí)行iadd指令時怨规,就會將最接近棧頂?shù)膬蓚€int元素取出并相加,然后將相加的結(jié)果再入棧锡足。
操作數(shù)棧中元素的數(shù)據(jù)類型必須與字節(jié)碼指令的序列嚴格匹配波丰,在編譯程序代碼的時候,編譯器要嚴格保證這一點舶得。比如剛才的iadd指令掰烟,它取出的元素必須是int的,不能出現(xiàn)諸如long和float類型的變量沐批。
雖然概念模型中不同棧幀之間是完全相互獨立的纫骑,但大多虛擬機實現(xiàn)中會有一些優(yōu)化處理:令兩個棧幀出現(xiàn)一部分重疊,讓下面的棧幀的部分操作數(shù)棧與上面棧幀的部分局部變量表重疊在一起重疊在一起九孩,這樣在進行方法調(diào)用時就可以公用一部分數(shù)據(jù)先馆,無須進行額外的參數(shù)復制傳遞,如圖所示:
2.1.3 動態(tài)鏈接
每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用躺彬,持有這個引用是為了支持方法調(diào)用過程中的動態(tài)連接煤墙。
靜態(tài)解析:我們知道Class文件的常量池中存有大量的符號引用,字節(jié)碼中的方法調(diào)用指令就以常量池中指向方法的符號引用作為參數(shù)顾患,這些符號引用一部分會在類加載階段或者第一次使用的時候就轉(zhuǎn)化為直接引用番捂,這種轉(zhuǎn)化稱為靜態(tài)解析。
動態(tài)鏈接:除去靜態(tài)解析的另外一部分將在每一次運行期間轉(zhuǎn)化為直接引用江解,這部分稱為動態(tài)鏈接设预。
所以要執(zhí)行某個方法時,某個指令(例如invokevirtual)將常量池中的引用作為參數(shù)犁河,而根據(jù)這個引用就可以找到真正的棧幀鳖枕。
關(guān)于方法的解析與調(diào)用,參考:https://blog.csdn.net/reachwang/article/details/103058653
2.1.4 方法出口
方法出口也可以通俗的理解為方法返回方式:在jvm中桨螺,方法返回方式有兩種:正常和異常宾符。
正常出口:當程序執(zhí)行遇到方法返回的字節(jié)碼指令,就完成此次方法執(zhí)行灭翔,并根據(jù)調(diào)用方指定的返回值去返回(可以無返回值)魏烫。
異常出口:方法在執(zhí)行中遇到了異常,并且在方法體內(nèi)沒有得到處理肝箱,會導致方法退出哄褒,這時候不會有任何返回值給調(diào)用方。
一個方法退出時需要返回到其被調(diào)用的位置煌张,上層調(diào)用方法才能繼續(xù)執(zhí)行呐赡。
程序正常退出時,相當于把當前棧幀出棧骏融,調(diào)用pc計數(shù)器的值作為返回地址链嘀,即調(diào)用該方法的指令的下一條指令的地址萌狂。
程序異常退出時:當程序發(fā)生異常時,返回地址需要通過異常表來確定怀泊,在棧幀中沒有保存異常表茫藏。
2.2 虛擬機棧與本地方法棧的關(guān)系
為了更好地理解虛擬機棧和本地方法棧的結(jié)構(gòu)模型以及關(guān)系,我們以網(wǎng)上的例子簡單描述下霹琼,如下圖:
三刷允、寄存器 (The pc Register)
Java虛擬機可以支持多個線程同時執(zhí)行,每個Java線程都有其自己的 pc(程序計數(shù)器)寄存器碧囊。在任何時候树灶,每個Java虛擬機線程都在執(zhí)行單個方法的代碼,即該線程的當前方法糯而。(如果不是native天通,則該pc寄存器包含當前正在執(zhí)行的Java虛擬機指令的地址。如果線程當前正在執(zhí)行的方法是native熄驼,則Java虛擬機的pc寄存器值未定義像寒。
pc寄存器中的值就是當前指令所在的內(nèi)存地址,即returnAddress類型的數(shù)據(jù)瓜贾,當線程執(zhí)行native方法時诺祸,pc中的值為undefined。
四祭芦、方法區(qū) (Method Area)
Java虛擬機具有一個在所有Java虛擬機線程之間共享的方法區(qū)域筷笨。該方法區(qū)域類似于常規(guī)語言的編譯代碼的存儲區(qū)域,或者類似于操作系統(tǒng)過程中的“文本”段龟劲。它存儲每個類的結(jié)構(gòu)胃夏,例如運行時常量池,字段和方法數(shù)據(jù)昌跌,以及方法和構(gòu)造函數(shù)的代碼仰禀,包括用于類和實例初始化以及接口初始化的特殊方法。
方法區(qū)是在虛擬機啟動時創(chuàng)建的蚕愤。盡管方法區(qū)在邏輯上是堆的一部分答恶,但是可以選擇不進行垃圾回收或壓縮。該規(guī)范沒有規(guī)定方法區(qū)域的位置或用于管理已編譯代碼的策略萍诱。方法區(qū)域可以是固定大小的悬嗓,或者可以根據(jù)計算的需要進行擴展,如果不需要更大的方法區(qū)域砂沛,則可以縮小烫扼。方法區(qū)域的內(nèi)存不必是連續(xù)的曙求。
可能拋出OutOfMemoryError異常碍庵。
五映企、堆 (Heap)
Java虛擬機具有一個在所有Java虛擬機線程之間共享的堆。堆是運行時數(shù)據(jù)區(qū)静浴,從中分配所有類實例和數(shù)組的內(nèi)存堰氓。
堆是在虛擬機啟動時創(chuàng)建的。對象的堆存儲由GC(垃圾收集器)回收苹享;對象永遠不會顯式釋放双絮。Java虛擬機可以根據(jù)實現(xiàn)者的系統(tǒng)要求選擇GC。堆的大小可以是固定的得问,也可以根據(jù)計算要求進行擴展囤攀,如果不需要更大的堆,則可以將其收縮宫纬。堆的內(nèi)存不必是連續(xù)的焚挠。
可能拋出OutOfMemoryError異常。
在jdk1.8之前的版本堆內(nèi)存空間是不同的漓骚,主要卻別在于:1.8中刪除了永久代蝌衔,新增了元空間。
元空間的本質(zhì)和永久代類似蝌蹂,都是對JVM規(guī)范中方法區(qū)的實現(xiàn)噩斟。不過元空間與永久代之間最大的區(qū)別在于:元空間并不在虛擬機中,而是使用本地內(nèi)存孤个。因此剃允,默認情況下,元空間的大小僅受本地內(nèi)存限制齐鲤,但可以通過參數(shù)來指定元空間的大小硅急。
jvm中的常量池
參考文章:https://blog.csdn.net/weixin_40999907/article/details/87907083
方法區(qū):運行時常量池
Class文件:常量池
堆:String常量池