【JVM之運(yùn)行時(shí)數(shù)據(jù)區(qū)1】程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧

一、運(yùn)行時(shí)數(shù)據(jù)區(qū)

我們?cè)诰帉?xiě)Java程序時(shí),使用JVM的流程主要如下所示:


在這里插入圖片描述

虛擬機(jī)在執(zhí)行Java程序時(shí),會(huì)把它所管理的內(nèi)存劃分為不同的數(shù)據(jù)區(qū)域,即運(yùn)行時(shí)數(shù)據(jù)區(qū)。有些數(shù)據(jù)區(qū)域是線程共享的,即這些區(qū)域會(huì)隨著虛擬機(jī)的啟動(dòng)而創(chuàng)建,隨著虛擬機(jī)的關(guān)閉而銷(xiāo)毀。而另一些區(qū)域則是與線程對(duì)應(yīng),屬于線程私有的。這些區(qū)域會(huì)隨著線程開(kāi)始而創(chuàng)建,隨著線程的結(jié)束而銷(xiāo)毀。

具體的劃分如下:
多個(gè)線程共享的:堆、方法區(qū)
每個(gè)線程私有的:程序計(jì)數(shù)器、Java虛擬機(jī)棧、本地方法棧

圖示如下:


在這里插入圖片描述

關(guān)于Java線程:在虛擬機(jī)中,每個(gè)線程都與操作系統(tǒng)的本地線程是直接對(duì)應(yīng)的。當(dāng)Java的線程準(zhǔn)備執(zhí)行時(shí),操作系統(tǒng)的線程也同時(shí)創(chuàng)建了。

二、程序計(jì)數(shù)器

程序計(jì)數(shù)器是一塊很小的內(nèi)存空間,可以認(rèn)為是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器,負(fù)責(zé)保存當(dāng)前線程正在執(zhí)行的字節(jié)碼地址。

1、程序計(jì)數(shù)器的作用

① Java是支持多線程的,這也意味著CPU會(huì)不停地切換線程。虛擬機(jī)的多線程是通過(guò)CPU時(shí)間片輪轉(zhuǎn)法來(lái)實(shí)現(xiàn)的,即每個(gè)線程占用CPU的時(shí)間是同等的,時(shí)間一到就會(huì)切換線程,如此重復(fù),直到線程終止。這也意味著某個(gè)線程會(huì)因?yàn)闀r(shí)間片到而被掛起,所以當(dāng)該線程再次獲得時(shí)間片時(shí),需要知道從哪個(gè)地方繼續(xù)執(zhí)行,此時(shí)虛擬機(jī)只需要讀取程序計(jì)數(shù)器的值就可以知道要執(zhí)行的字節(jié)碼指令的位置了。這也是程序計(jì)數(shù)器是線程私有的原因,因?yàn)槿舫绦蛴?jì)數(shù)器不是線程私有的話(huà),當(dāng)CPU切換線程時(shí),會(huì)按照上一個(gè)線程的字節(jié)碼指令的位置來(lái)執(zhí)行當(dāng)前線程的字節(jié)碼指令,這顯然是不正確的
② JVM的字節(jié)碼解釋器也需要通過(guò)改變程序計(jì)數(shù)器的值來(lái)明確下一條字節(jié)碼指令。

2、程序計(jì)數(shù)器的特點(diǎn)

① 程序計(jì)數(shù)器是數(shù)據(jù)區(qū)中唯一沒(méi)有規(guī)定OutOfMemoryError(內(nèi)存溢出異常)的區(qū)域。
②如果一個(gè)線程執(zhí)行的是Native本地方法,那么程序計(jì)數(shù)器的值為undefined。因?yàn)镴VM在執(zhí)行Native本地方法時(shí),是通過(guò)JNI調(diào)用本地其他語(yǔ)言來(lái)實(shí)現(xiàn)的,而不是字節(jié)碼。

三、虛擬機(jī)棧

棧是運(yùn)行時(shí)的單位,堆是存儲(chǔ)的單位。
棧解決程序的運(yùn)行問(wèn)題,即方法如何執(zhí)行(或者如何處理數(shù)據(jù))。
堆解決的是數(shù)據(jù)存儲(chǔ)問(wèn)題,即數(shù)據(jù)怎么放,在那放。

1、什么是虛擬機(jī)棧

虛擬機(jī)棧在每個(gè)線程開(kāi)始時(shí)隨之創(chuàng)建,虛擬機(jī)棧與數(shù)據(jù)結(jié)構(gòu)中的棧一致,也是遵循先進(jìn)后出的原則。
虛擬機(jī)棧進(jìn)行出棧入棧操作的元素就是棧幀。每一個(gè)棧幀就對(duì)應(yīng)著一個(gè)方法,也可以將棧幀理解為一個(gè)方法的運(yùn)行空間。
一個(gè)棧幀入棧,意味著一個(gè)方法被調(diào)用,一個(gè)棧幀出棧,即一個(gè)方法執(zhí)行完畢。則棧幀的入棧順序就是方法的調(diào)用順序。

有代碼:

public class VMStackTest {

    private static int i = 1;

    public static void main(String[] args) {
        VMStackTest test = new VMStackTest();
        test.add();
        System.out.println(i);
    }

    public void add(){
        i++;
    }
}

代碼所示的虛擬機(jī)棧示例如下:

在這里插入圖片描述

同一時(shí)刻,在同一線程中,只有位于虛擬機(jī)棧頂部的棧幀才是有效運(yùn)行的,即只有位于棧頂?shù)姆椒ㄊ钦趫?zhí)行的。執(zhí)行引擎運(yùn)行的所有字節(jié)碼指令都只針對(duì)當(dāng)前棧幀進(jìn)行操作。 如果在被調(diào)用的A方法中,又調(diào)用了B方法,那么對(duì)應(yīng)的B方法的棧幀就會(huì)被創(chuàng)建,并被放在虛擬機(jī)棧的棧頂,成為新的正在執(zhí)行的方法。

對(duì)于虛擬機(jī)棧來(lái)說(shuō),不存在垃圾回收問(wèn)題。

Java方法有兩種方法返回方式,一種是正常的方法返回,使用return指令;另一種是拋出異常。不管是哪一種,都會(huì)導(dǎo)致棧幀被彈出。

2、棧幀的內(nèi)部結(jié)構(gòu)

每個(gè)棧幀都存儲(chǔ)著:
① 局部變量表
②操作數(shù)棧
③方法返回地址
④動(dòng)態(tài)鏈接
⑤一些附加信息

圖示如下:


在這里插入圖片描述

(1)、局部變量表
局部變量表定義為一個(gè)數(shù)字?jǐn)?shù)組,用于存放方法參數(shù)和方法內(nèi)定義的局部變量。
局部變量表建立在線程的虛擬機(jī)棧上,是線程的私有數(shù)據(jù),因此不存在數(shù)據(jù)安全問(wèn)題。
虛擬機(jī)通過(guò)索引定位的方法查找相應(yīng)的局部變量,索引的范圍是從0~局部變量表最大容量。
局部變量表所需的容量大小是在編譯期定下的,方法運(yùn)行期間是不會(huì)該變局部變量表的大小的。

(2)、操作數(shù)棧
操作數(shù)棧也是一個(gè)棧的數(shù)據(jù)結(jié)構(gòu),操作數(shù)棧的最大深度也在編譯的時(shí)候定下。
操作數(shù)棧的每一個(gè)元素可以是任意Java數(shù)據(jù)類(lèi)型,在方法執(zhí)行過(guò)程中,操作數(shù)棧的深度都不會(huì)超過(guò)最大值。
操作數(shù)棧的作用是: 隨著方法執(zhí)行和字節(jié)碼指令的執(zhí)行,會(huì)從局部變量表或?qū)ο髮?shí)例的字段中復(fù)制常量或變量寫(xiě)入到操作數(shù)棧,再隨著計(jì)算的進(jìn)行將棧中元素出棧到局部變量表或者返回給方法調(diào)用者,也就是出棧/入棧操作。一個(gè)完整的方法執(zhí)行期間往往包含多個(gè)這樣出棧/入棧的過(guò)程。

(3)、方法返回地址
一個(gè)方法結(jié)束,有正常執(zhí)行完畢退出和拋出異常(沒(méi)catch),非正常退出兩種。
無(wú)論是那種方式,在退出后都會(huì)返回到該方法被調(diào)用的位置。
正常退出時(shí),調(diào)用者的程序計(jì)數(shù)器的值會(huì)作為返回地址,即調(diào)用該方法的指令的下一條指令的地址。但是異常退出時(shí),返回地址要通過(guò)異常表來(lái)確定。
實(shí)際上,方法的退出就是當(dāng)前棧幀出棧的過(guò)程。故需要恢復(fù)上一層棧幀的局部變量表、操作數(shù)棧、將返回值壓入調(diào)用者棧幀的操作數(shù)棧,并設(shè)置其程序計(jì)數(shù)器的值等,讓調(diào)用者棧幀繼續(xù)執(zhí)行下去。
兩種方法結(jié)束的區(qū)別是:因拋出異常而退出的方法不會(huì)給調(diào)用他的調(diào)用者棧幀任何返回值

3、虛擬機(jī)棧的常見(jiàn)異常

《Java虛擬機(jī)規(guī)范》中,允許虛擬機(jī)棧的大小是動(dòng)態(tài)擴(kuò)展的或者固定不變的。
這意味著,虛擬機(jī)棧會(huì)出現(xiàn)兩種異常:
StackOverflowError異常(棧溢出):
如果線程請(qǐng)求分配的棧容量超過(guò)Java虛擬機(jī)的最大容量時(shí),就會(huì)拋出棧溢出異常。最簡(jiǎn)單的示例就是不停遞歸而不退出,就會(huì)報(bào) StackOverflowError
當(dāng)虛擬機(jī)棧的大小為固定時(shí),易出現(xiàn)該異常。

OutOfMemoryError異常(內(nèi)存溢出)
當(dāng)虛擬機(jī)棧采用動(dòng)態(tài)擴(kuò)展時(shí),一定程度上可以規(guī)避 StackOverflowError,但是虛擬機(jī)給每個(gè)線程分配到的內(nèi)存空間是有限的,這也意味著隸屬于線程的虛擬機(jī)棧的內(nèi)存也是有限的,所以當(dāng)虛擬機(jī)棧請(qǐng)求擴(kuò)展的內(nèi)存大小無(wú)法滿(mǎn)足時(shí),就會(huì)報(bào)該異常。
但是并不意味著,采用固定大小的虛擬機(jī)棧就不會(huì)報(bào)該異常,如果某個(gè)棧幀太大,超出虛擬機(jī)棧所有的內(nèi)存也是可能的。

四、本地方法棧

(1)、本地方法
一個(gè)Native Method就是一個(gè)Java調(diào)用非Java代碼的接口。在定義一個(gè)本地方法時(shí),并不提供實(shí)現(xiàn)體(有些就像定義一個(gè)Java interface),因?yàn)閷?shí)現(xiàn)體是由非Java語(yǔ)言在外面實(shí)現(xiàn)的。本地接口的作用是融合不同的編程語(yǔ)言為Java所用。
(2)、本地方法棧
虛擬機(jī)棧是用于管理Java方法的調(diào)用,本地方法棧是用于管理本地方法的調(diào)用。
①本地方法棧是線程私有的
②本地方法棧也是可實(shí)現(xiàn)固定或者可動(dòng)態(tài)擴(kuò)展的內(nèi)存大?。ㄟ@也意味著本地方法棧的異常也與虛擬機(jī)棧一致)
③本地方法棧存放著被native關(guān)鍵字標(biāo)記的方法,在執(zhí)行引擎執(zhí)行時(shí)加載本地方法庫(kù)。
④并不是所有的JVM都支持本地方法,故也無(wú)需實(shí)現(xiàn)本地方法棧
⑤在HotSpot JVM中,直接將本地方法和虛擬機(jī)棧合二為一。

五、擴(kuò)展--設(shè)置棧內(nèi)存大小

有代碼如下:

public class StackOverError {
    private static int count = 1;
    public static void main(String[] args) {
        System.out.println(count);
        count++;
        main(args);
    }
}

在代碼中采用了遞歸,以count++的次數(shù)來(lái)查看目前虛擬機(jī)棧的深度。其結(jié)果如下:

在這里插入圖片描述

可見(jiàn)默認(rèn)情況下,count的值為2467。
我們可以使用
-Xss:來(lái)規(guī)定了每個(gè)線程虛擬機(jī)棧的內(nèi)存大小
如下,
在這里插入圖片描述

設(shè)置棧的大?。?-Xss1m或者-Xss1k (設(shè)置方法IDEA:Run --> Edit Configuration --> VM option)

結(jié)果如下,

在這里插入圖片描述

可見(jiàn)當(dāng)設(shè)置虛擬機(jī)棧的內(nèi)存大小為2m時(shí),count的值為233424。

故可看出使用參數(shù) -Xss可以設(shè)置線程的最大棧空間,且虛擬機(jī)棧的大小直接決定了函數(shù)調(diào)用的大小。

參考:
知乎-JVM虛擬機(jī)棧執(zhí)行原理深入詳解

2020最新-JVM-Java虛擬機(jī)-從入門(mén)到精通-尚硅谷

知乎-Java虛擬機(jī)—棧幀、操作數(shù)棧和局部變量表

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容