JVM 中的內(nèi)存可以劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域,主要分為:程序計(jì)數(shù)器伦仍、虛擬機(jī)棧结窘、本地方法棧、堆充蓝、方法區(qū)晦鞋。
Java程序是多線程的,CPU可以在多個(gè)線程中分配執(zhí)行時(shí)間片段棺克。當(dāng)某一個(gè)線程被CPU掛起時(shí)悠垛,需要記錄代碼已經(jīng)執(zhí)行到的位置,方便CPU重新執(zhí)行此線程時(shí)娜谊,知道從哪行指令開(kāi)始執(zhí)行确买。這就是程序計(jì)??????數(shù)器的作用。
“程序計(jì)數(shù)器”是虛擬機(jī)中一塊較小的內(nèi)存空間纱皆,主要用于記錄當(dāng)前線程執(zhí)行的位置湾趾。
程序計(jì)數(shù)器需要注意的點(diǎn)
- 在Java虛擬機(jī)規(guī)范中芭商,對(duì)程序計(jì)數(shù)器這一區(qū)域沒(méi)有規(guī)定任何OutOfMemoryError情況(或許是感覺(jué)沒(méi)有必要吧)。
- 程序計(jì)數(shù)器是線程私有的搀缠,每條線程內(nèi)部都有一個(gè)私有程序計(jì)數(shù)器铛楣。它的生命周期隨著線程的創(chuàng)建而創(chuàng)建,隨著線程的結(jié)束而死亡艺普。
- 當(dāng)一個(gè)線程正在執(zhí)行一個(gè) Java 方法的時(shí)候簸州,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址。如果正在執(zhí)行的是 Native 方法歧譬,這個(gè)計(jì)數(shù)器值則為空(Undefined)岸浑。
虛擬機(jī)棧
在Java虛擬機(jī)規(guī)范中,對(duì)這個(gè)區(qū)域規(guī)定了兩種異常狀況:
- StackOverflowError:當(dāng)線程請(qǐng)求棧深度超出虛擬機(jī)棧所允許的深度時(shí)拋出瑰步。
- OutOfMemoryError:當(dāng) Java 虛擬機(jī)動(dòng)態(tài)擴(kuò)展到無(wú)法申請(qǐng)足夠內(nèi)存時(shí)拋出矢洲。
JVM 是基于棧的解釋器執(zhí)行的,DVM 是基于寄存器解釋器執(zhí)行的缩焦。
棧的解釋器這要從JVM內(nèi)存虛擬模型開(kāi)始
JVM 會(huì)給每個(gè)方法創(chuàng)建一個(gè)棧幀读虏,我們可以這樣理解:一個(gè)線程包含多個(gè)棧幀,而每個(gè)棧幀內(nèi)部包含局部變量表袁滥、操作數(shù)棧掘譬、動(dòng)態(tài)連接、返回地址等呻拌;
基于寄存器的虛擬機(jī)葱轩,它們的操作數(shù)是存放在CPU的寄存器的。
通過(guò)操作數(shù)的地址藐握,對(duì)數(shù)據(jù)直接進(jìn)行添加靴拱,指令比jvm少
public static int add(int k) {
int i = 1;
int j = 2;
return i + j + k;
}
使用javac javap -v 得到
在這里推薦ASM Bytecode Viewer
系統(tǒng)不會(huì)為局部變量賦予初始值(實(shí)例變量和類(lèi)變量都會(huì)被賦予初始值),也就是說(shuō)不存在類(lèi)變量那樣的準(zhǔn)備階段猾普。
操作數(shù)棧(lifo)袜炕,后入先出
當(dāng)一個(gè)方法剛剛開(kāi)始執(zhí)行的時(shí)候,這個(gè)方法的操作數(shù)棧是空的初家。在方法執(zhí)行的過(guò)程中偎窘,會(huì)有各種字節(jié)碼指令被壓入和彈出操作數(shù)棧(比如:iadd指令就是將操作數(shù)棧中棧頂?shù)膬蓚€(gè)元素彈出,執(zhí)行加法運(yùn)算溜在,并將結(jié)果重新壓回到操作數(shù)棧中
動(dòng)態(tài)鏈接
動(dòng)態(tài)鏈接的主要目的是為了支持方法調(diào)用過(guò)程中的動(dòng)態(tài)連接(Dynamic Linking)陌知。
在一個(gè) class 文件中,一個(gè)方法要調(diào)用其他方法掖肋,需要將這些方法的符號(hào)引用轉(zhuǎn)化為其所在內(nèi)存地址中的直接引用仆葡,而符號(hào)引用存在于方法區(qū)中。
返回地址
- 正常退出:指方法中的代碼正常完成志笼,或者遇到任意一個(gè)方法返回的字節(jié)碼指令(如return)并退出沿盅,沒(méi)有拋出任何異常把篓。
- 異常退出:指方法執(zhí)行過(guò)程中遇到異常,并且這個(gè)異常在方法體內(nèi)部沒(méi)有得到處理腰涧,導(dǎo)致方法退出韧掩。
public static int add() {
int i = 1;
int j = 2;
return i + j + 10;
}
0:iconst_1(把常量1壓入操作數(shù)棧棧頂)
1:istore_1(把操作數(shù)棧棧頂?shù)某鰲7湃刖植孔兞勘硭饕秊?的位置) 2: iconst_2 (把常量 2 壓入操作數(shù)棧棧頂)
3: istore_2 (把操作數(shù)棧棧頂?shù)某鰲7湃刖植孔兞勘硭饕秊?2 的位置)
4: iload_1 (把局部變量表索引為 1 的值放入操作數(shù)棧棧頂)
5: iload_2 (把局部變量表索引為 2 的值放入操作數(shù)棧棧頂)
6: iadd (將操作數(shù)棧棧頂?shù)暮蜅m斚旅娴囊粋€(gè)進(jìn)行加法運(yùn)算后放入棧頂)
7: istore_3 (把操作數(shù)棧棧頂?shù)某鰲7湃刖植孔兞勘硭饕秊?3 的位置)
8: iload_3 (把局部變量表索引為 3 的值放入操作數(shù)棧棧頂)
9: bipush 10 (把常量 10 壓入操作數(shù)棧棧頂)
11: iadd (將操作數(shù)棧棧頂?shù)暮蜅m斚旅娴囊粋€(gè)進(jìn)行加法運(yùn)算后放入棧頂)
12: ireturn (結(jié)束)
所以當(dāng)方法運(yùn)行完后,局部變量表窖铡,操作數(shù)棧都會(huì)被銷(xiāo)毀疗锐,所以這一塊的內(nèi)存不需要我們管。
堆(是我們需要重點(diǎn)關(guān)注的點(diǎn))
所有對(duì)象實(shí)例都在這里,注意多線程
總結(jié)來(lái)說(shuō)万伤,JVM的運(yùn)行時(shí)內(nèi)存結(jié)構(gòu)中一共有兩個(gè)“椫匣冢”和一個(gè)“堆”呜袁,分別是:Java虛擬機(jī)棧和本地方法棧敌买,以及“GC堆”和方法區(qū)。除此之外還有一個(gè)程序計(jì)數(shù)器阶界,但是我們開(kāi)發(fā)者幾乎不會(huì)用到這一部分虹钮,所以并不是重點(diǎn)學(xué)習(xí)內(nèi)容。JVM內(nèi)存中只有堆和方法區(qū)是線程共享的數(shù)據(jù)區(qū)域膘融,其它區(qū)域都是線程私有的芙粱。并且程序計(jì)數(shù)器是唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒(méi)有規(guī)定任何OutOfMemoryError情況區(qū)域