詳解Java的內(nèi)存模型
說(shuō)JVM內(nèi)存模型之前,先聊一個(gè)老生常談的問(wèn)題,為什么Java可以 “一次編譯,到處運(yùn)行”,這個(gè)話題最直接的答案就是,因?yàn)镴ava有JVM啊,解釋這個(gè)答案之前,我想先回顧一下一個(gè)語(yǔ)言被編譯的過(guò)程:
一般編程語(yǔ)言的編譯過(guò)程大抵就是,編譯——連接——執(zhí)行,這里的編譯就是,把我們寫(xiě)的源代碼,根據(jù)語(yǔ)義語(yǔ)法進(jìn)行翻譯,形成目標(biāo)代碼,即匯編碼。再由匯編程序翻譯成機(jī)器語(yǔ)言(可以理解為直接運(yùn)行于硬件上的01語(yǔ)言);然后進(jìn)行連接,所謂連接就是將目標(biāo)代碼與函數(shù)庫(kù)相連接,并將源程序所用的庫(kù)代碼與目標(biāo)代碼合并,并形成最終可執(zhí)行的二進(jìn)制機(jī)器代碼(程序)。
編譯運(yùn)行的整個(gè)流程,有一個(gè)前提,那就是到匯編的層面,指令編碼就和處理器的架構(gòu)強(qiáng)關(guān)聯(lián)了,說(shuō)白點(diǎn)就是和硬件關(guān)聯(lián)了,可以粗暴的理解為,一類(lèi)硬件機(jī)器只認(rèn)識(shí)一種匯編,一種機(jī)器只認(rèn)一種機(jī)器碼。在這個(gè)基礎(chǔ)下,很容易就會(huì)發(fā)現(xiàn)一個(gè)問(wèn)題,一個(gè)編程語(yǔ)言經(jīng)過(guò)編譯、連接形成的可運(yùn)行的機(jī)器碼X,可以在硬件環(huán)境1的情況下運(yùn)行,當(dāng)機(jī)器碼X到硬件環(huán)境2,就未必可以運(yùn)行了,或者說(shuō)運(yùn)行結(jié)果就不是硬件環(huán)境1的結(jié)果了,所以,同一個(gè)程序,換臺(tái)PC,我們就可能需要重新編譯、打包成可運(yùn)行在當(dāng)前硬件環(huán)境的程序。這樣在工程化運(yùn)用中真的是災(zāi)難。
現(xiàn)在我們回到開(kāi)篇問(wèn)題的答案,之所以Java可以“一次編譯,到處運(yùn)行”,是因?yàn)橛蠮VM,為了便于理解,我們可以這樣認(rèn)為:JVM就是一個(gè)完備的中間環(huán)境,它提供編譯運(yùn)行Java字節(jié)碼的全套環(huán)境,換句話說(shuō),它就像一個(gè)小隔離空間,我的Java程序只要編譯一次,只要滿(mǎn)足可以跑在JVM中,那它就可以隨便移植在任何硬件環(huán)境中,所以Java的“一次編譯,到處運(yùn)行”的本質(zhì)就是,它處處都要依賴(lài)JVM,它其實(shí)就是一個(gè)運(yùn)行在JVM中的寄生蟲(chóng),這也是為什么想要運(yùn)行環(huán)境,你就必須要裝JDK的原因。
上面的理解只不過(guò)是為了更快的入戲,但是上面的理解過(guò)于粗暴,下面細(xì)膩一下JVM的性質(zhì)以及它所處的位置:
通常工作中所接觸的基本是Java庫(kù)和應(yīng)用以及Java核心類(lèi)庫(kù),知道如何使用就可以了,但是歸根結(jié)底代碼都是要編譯成class文件由Java虛擬機(jī)裝載執(zhí)行,所產(chǎn)生的結(jié)果或者現(xiàn)象都可以通過(guò)Java虛擬機(jī)的運(yùn)行機(jī)制來(lái)解釋。一些相同的代碼會(huì)由于虛擬機(jī)的實(shí)現(xiàn)不同而產(chǎn)生不同結(jié)果。
然后是我們要介紹的JVM,首先我們要明確一個(gè)概念,JVM它并不是某一個(gè)具體的產(chǎn)品,也不是一個(gè)成品的軟件,更準(zhǔn)確地說(shuō)JVM是一種理論規(guī)范,對(duì)JVM的具體實(shí)現(xiàn)要么是軟件,要么是軟件和硬件的組合,JVM可以由不同的廠商來(lái)實(shí)現(xiàn)成不同的產(chǎn)品。由于廠商的不同必然導(dǎo)致JVM在實(shí)現(xiàn)上的一些不同,像國(guó)內(nèi)就有著名的TaobaoVM;
在Java平臺(tái)的結(jié)構(gòu)中,可以看出,Java虛擬機(jī)(JVM)處在核心的位置,是程序與底層操作系統(tǒng)和硬件無(wú)關(guān)的關(guān)鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統(tǒng),其中依賴(lài)于平臺(tái)的部分稱(chēng)為適配器;JVM通過(guò)移植接口在具體的平臺(tái)和操作系統(tǒng)上實(shí)現(xiàn);在JVM的上方是Java的基本類(lèi)庫(kù)和擴(kuò)展類(lèi)庫(kù)以及它們的API, 利用Java API編寫(xiě)的應(yīng)用程序(application)和小程序(Java applet)可以在任何Java平臺(tái)上運(yùn)行而無(wú)需考慮底層平臺(tái),就是因?yàn)橛蠮ava虛擬機(jī)(JVM)實(shí)現(xiàn)了程序與操作系統(tǒng)的分離,從而實(shí)現(xiàn)了Java的平臺(tái)無(wú)關(guān)性。
JVM在它的生存周期中有一個(gè)明確的任務(wù),那就是裝載字節(jié)碼文件,一旦字節(jié)碼進(jìn)入虛擬機(jī),它就會(huì)被解釋器解釋執(zhí)行,或者是被即時(shí)代碼發(fā)生器有選擇的轉(zhuǎn)換成機(jī)器碼執(zhí)行,即Java程序被執(zhí)行。因此當(dāng)Java程序啟動(dòng)的時(shí)候,就產(chǎn)生JVM的一個(gè)實(shí)例;當(dāng)程序運(yùn)行結(jié)束的時(shí)候,該實(shí)例也跟著消失了。
JVM的內(nèi)存模型總覽總體來(lái)講,JVM會(huì)將Java進(jìn)程所管理的內(nèi)存劃分為若干不同的數(shù)據(jù)區(qū)域. 這些區(qū)域有各自的用途、創(chuàng)建/銷(xiāo)毀時(shí)間。以上這張圖,就是Java的編譯運(yùn)行過(guò)程,上半部分(運(yùn)行時(shí)區(qū)域)其實(shí)就是JVM的內(nèi)存分配,它把從操作系統(tǒng)獲取來(lái)的內(nèi)存空間進(jìn)行了獨(dú)立的劃分,分別為方法區(qū)、堆、虛擬機(jī)棧、本地方法棧、程序計(jì)數(shù)器。下半部分就是連接——運(yùn)行階段的,JVM將Java語(yǔ)言處理完畢,變成適配與當(dāng)前機(jī)器的機(jī)器碼,然后與本地庫(kù)進(jìn)行連接,運(yùn)行。
線程私有區(qū)域線程私有數(shù)據(jù)區(qū)域生命周期與線程相同, 依賴(lài)用戶(hù)線程的啟動(dòng)/結(jié)束而創(chuàng)建/銷(xiāo)毀(在Hotspot VM內(nèi), 每個(gè)線程都與操作系統(tǒng)的本地線程直接映射, 因此這部分內(nèi)存區(qū)域的存/否跟隨本地線程的生/死)。
程序計(jì)數(shù)器
一塊較小的內(nèi)存空間, 作用是當(dāng)前線程所執(zhí)行字節(jié)碼的行號(hào)指示器(類(lèi)似于傳統(tǒng)CPU模型中的PC), PC在每次指令執(zhí)行后自增, 維護(hù)下一個(gè)將要執(zhí)行指令的地址. 在JVM模型中, 字節(jié)碼解釋器就是通過(guò)改變PC值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴(lài)PC完成(僅限于Java方法, Native方法該計(jì)數(shù)器值為undefined).不同于OS以進(jìn)程為單位調(diào)度, JVM中的并發(fā)是通過(guò)線程切換并分配時(shí)間片執(zhí)行來(lái)實(shí)現(xiàn)的. 在任何一個(gè)時(shí)刻, 一個(gè)處理器內(nèi)核只會(huì)執(zhí)行一條線程中的指令. 因此, 為了線程切換后能恢復(fù)到正確的執(zhí)行位置, 每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器, 這類(lèi)內(nèi)存被稱(chēng)為“線程私有”內(nèi)存。
JAVA代碼編譯后的字節(jié)碼在未經(jīng)過(guò)JIT(實(shí)時(shí)編譯器)編譯前,其執(zhí)行方式是通過(guò)“字節(jié)碼解釋器”進(jìn)行解釋執(zhí)行。簡(jiǎn)單的工作原理為解釋器讀取裝載入內(nèi)存的字節(jié)碼,按照順序讀取字節(jié)碼指令。讀取一個(gè)指令后,將該指令“翻譯”成固定的操作,并根據(jù)這些操作進(jìn)行分支、循環(huán)、跳轉(zhuǎn)等流程。
從上面的描述中,可能會(huì)產(chǎn)生程序計(jì)數(shù)器是否是多余的疑問(wèn)。因?yàn)檠刂噶畹捻樞驁?zhí)行下去,即使是分支跳轉(zhuǎn)這樣的流程,跳轉(zhuǎn)到指定的指令處按順序繼續(xù)執(zhí)行是完全能夠保證程序的執(zhí)行順序的。假設(shè)程序永遠(yuǎn)只有一個(gè)線程,這個(gè)疑問(wèn)沒(méi)有任何問(wèn)題,也就是說(shuō)并不需要程序計(jì)數(shù)器。但實(shí)際上程序是通過(guò)多個(gè)線程協(xié)同合作執(zhí)行的。
首先我們要搞清楚JVM的多線程實(shí)現(xiàn)方式。JVM的多線程是通過(guò)CPU時(shí)間片輪轉(zhuǎn)(即線程輪流切換并分配處理器執(zhí)行時(shí)間)算法來(lái)實(shí)現(xiàn)的。也就是說(shuō),某個(gè)線程在執(zhí)行過(guò)程中可能會(huì)因?yàn)闀r(shí)間片耗盡而被掛起,而另一個(gè)線程獲取到時(shí)間片開(kāi)始執(zhí)行。當(dāng)被掛起的線程重新獲取到時(shí)間片的時(shí)候,它要想從被掛起的地方繼續(xù)執(zhí)行,就必須知道它上次執(zhí)行到哪個(gè)位置,在JVM中,通過(guò)程序計(jì)數(shù)器來(lái)記錄某個(gè)線程的字節(jié)碼執(zhí)行位置。因此,程序計(jì)數(shù)器是具備線程隔離的特性,也就是說(shuō),每個(gè)線程工作時(shí)都有屬于自己的獨(dú)立計(jì)數(shù)器。
程序計(jì)數(shù)器的特點(diǎn)
1.線程隔離性,每個(gè)線程工作時(shí)都有屬于自己的獨(dú)立計(jì)數(shù)器。
2.執(zhí)行java方法時(shí),程序計(jì)數(shù)器是有值的,且記錄的是正在執(zhí)行的字節(jié)碼指令的地址(參考上一小節(jié)的描述)。
3.執(zhí)行native本地方法時(shí),程序計(jì)數(shù)器的值為空(Undefined)。因?yàn)閚ative方法是java通過(guò)JNI直接調(diào)用本地C/C++庫(kù),可以近似的認(rèn)為native方法相當(dāng)于C/C++暴露給java的一個(gè)接口,java通過(guò)調(diào)用這個(gè)接口從而調(diào)用到C/C++方法。由于該方法是通過(guò)C/C++而不是java進(jìn)行實(shí)現(xiàn)。那么自然無(wú)法產(chǎn)生相應(yīng)的字節(jié)碼,并且C/C++執(zhí)行時(shí)的內(nèi)存分配是由自己語(yǔ)言決定的,而不是由JVM決定的。
4.程序計(jì)數(shù)器占用內(nèi)存很小,在進(jìn)行JVM內(nèi)存計(jì)算時(shí),可以忽略不計(jì)。
5.程序計(jì)數(shù)器,是唯一一個(gè)在java虛擬機(jī)規(guī)范中沒(méi)有規(guī)定任何OutOfMemoryError的區(qū)域。
虛擬機(jī)棧
這里的虛擬機(jī)棧主要是針對(duì)Java的方法執(zhí)行,我們都知道方法在編程中使用的是棧的數(shù)據(jù)結(jié)構(gòu);每個(gè)方法被執(zhí)行時(shí)會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame)用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息. 每個(gè)方法被調(diào)用至返回的過(guò)程, 就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過(guò)程(VM提供了-Xss來(lái)指定線程的最大棧空間, 該參數(shù)也直接決定了函數(shù)調(diào)用的最大深度)。這里特別說(shuō)明一下局部變量表,這里的局部變量表,其實(shí)就是我們定義的方法內(nèi)部的變量,它基本的范圍包括基本數(shù)據(jù)類(lèi)型(如boolean、int、double等) 、對(duì)象引用(reference : 不等同于對(duì)象本身, 可能是一個(gè)指向?qū)ο笃鹗嫉刂返闹羔? 也可能指向一個(gè)代表對(duì)象的句柄或其他與此對(duì)象相關(guān)的位置),也就是我們私下常說(shuō)的‘堆棧’中的‘棧’。
Java虛擬機(jī)使用局部變量表來(lái)完成方法調(diào)用時(shí)的參數(shù)傳遞。局部變量表的長(zhǎng)度在編譯期已經(jīng)決定了并存儲(chǔ)于類(lèi)和接口的二進(jìn)制表示中,一個(gè)局部變量可以保存一個(gè)類(lèi)型為boolean、byte、char、short、float、reference和returnAddress的數(shù)據(jù),兩個(gè)局部變量可以保存一個(gè)類(lèi)型為long和double的數(shù)據(jù)。
Java虛擬機(jī)提供一些字節(jié)碼指令來(lái)從局部變量表或者對(duì)象實(shí)例的字段中復(fù)制常量或變量值到操作數(shù)棧中,也提供了一些指令用于從操作數(shù)棧取走數(shù)據(jù)、操作數(shù)據(jù)和把操作結(jié)果重新入棧。在方法調(diào)用的時(shí)候,操作數(shù)棧也用來(lái)準(zhǔn)備調(diào)用方法的參數(shù)以及接收方法返回結(jié)果。
每個(gè)棧幀中都包含一個(gè)指向運(yùn)行時(shí)常量區(qū)的引用支持當(dāng)前方法的動(dòng)態(tài)鏈接。在Class文件中,方法調(diào)用和訪問(wèn)成員變量都是通過(guò)符號(hào)引用來(lái)表示的,動(dòng)態(tài)鏈接的作用就是將符號(hào)引用轉(zhuǎn)化為實(shí)際方法的直接引用或者訪問(wèn)變量的運(yùn)行是內(nèi)存位置的正確偏移量。
總的來(lái)說(shuō),Java虛擬機(jī)棧是用來(lái)存放局部變量和過(guò)程結(jié)果的地方。Java虛擬機(jī)棧可能發(fā)生如下異常情況: 如果Java虛擬機(jī)棧被實(shí)現(xiàn)為固定大小內(nèi)存,線程請(qǐng)求分配的棧容量超過(guò)Java虛擬機(jī)棧允許的最大容量時(shí),Java虛擬機(jī)將會(huì)拋出一個(gè)StackOverflowError異常。如果Java虛擬機(jī)棧被實(shí)現(xiàn)為動(dòng)態(tài)擴(kuò)展內(nèi)存大小,并且擴(kuò)展的動(dòng)作已經(jīng)嘗試過(guò),但是目前無(wú)法申請(qǐng)到足夠的內(nèi)存去完成擴(kuò)展,或者在建立新的線程時(shí)沒(méi)有足夠的內(nèi)存去創(chuàng)建對(duì)應(yīng)的虛擬機(jī)棧,那Java虛擬機(jī)將會(huì)拋出一個(gè)OutOfMemoryError異常。
1.符號(hào)引用(Symbolic References):
符號(hào)引用以一組符號(hào)來(lái)描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量,只要使用時(shí)能夠無(wú)歧義的定位到目標(biāo)即可。例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等類(lèi)型的常量出現(xiàn)。符號(hào)引用與虛擬機(jī)的內(nèi)存布局無(wú)關(guān),引用的目標(biāo)并不一定加載到內(nèi)存中。在Java中,一個(gè)java類(lèi)將會(huì)編譯成一個(gè)class文件。在編譯時(shí),java類(lèi)并不知道所引用的類(lèi)的實(shí)際地址,因此只能使用符號(hào)引用來(lái)代替。比如org.simple.People類(lèi)引用了org.simple.Language類(lèi),在編譯時(shí)People類(lèi)并不知道Language類(lèi)的實(shí)際內(nèi)存地址,因此只能使用符號(hào)org.simple.Language(假設(shè)是這個(gè),當(dāng)然實(shí)際中是由類(lèi)似于CONSTANT_Class_info的常量來(lái)表示的)來(lái)表示Language類(lèi)的地址。各種虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局可能有所不同,但是它們能接受的符號(hào)引用都是一致的,因?yàn)榉?hào)引用的字面量形式明確定義在Java虛擬機(jī)規(guī)范的Class文件格式中。
2.直接引用:
直接引用可以是
(1)直接指向目標(biāo)的指針(比如,指向“類(lèi)型”【Class對(duì)象】、類(lèi)變量、類(lèi)方法的直接引用可能是指向方法區(qū)的指針)
(2)相對(duì)偏移量(比如,指向?qū)嵗兞俊?shí)例方法的直接引用都是偏移量)
(3)一個(gè)能間接定位到目標(biāo)的句柄
直接引用是和虛擬機(jī)的布局相關(guān)的,同一個(gè)符號(hào)引用在不同的虛擬機(jī)實(shí)例上翻譯出來(lái)的直接引用一般不會(huì)相同。如果有了直接引用,那引用的目標(biāo)必定已經(jīng)被加載入內(nèi)存中了。
本地方法棧
本地方法棧其實(shí)作用和虛擬機(jī)棧的作用一樣,不同的是,虛擬機(jī)棧是為虛擬機(jī)解析運(yùn)行Java方法,而本地方法棧是為虛擬機(jī)調(diào)用Native方法服務(wù)(Native方法簡(jiǎn)單點(diǎn)來(lái)說(shuō)就是一個(gè)java調(diào)用非java代碼的接口。一個(gè)Native 方法是這樣一個(gè)java的方法:該方法的實(shí)現(xiàn)由非java語(yǔ)言實(shí)現(xiàn))
線程共享區(qū)域這一區(qū)域的生命周期,同虛擬機(jī)一致,也就是虛擬機(jī)內(nèi)部的公共內(nèi)存區(qū)域,隨虛擬機(jī)的啟動(dòng)/關(guān)閉而創(chuàng)建/銷(xiāo)毀
堆區(qū)
這里的堆,是虛擬機(jī)從操作系統(tǒng)那里申請(qǐng)來(lái)的的內(nèi)存空間,這塊空間是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊,并且是所有線程共享的一塊內(nèi)存區(qū)域,Java堆在虛擬機(jī)啟動(dòng)的時(shí)候被創(chuàng)建,主要用來(lái)為類(lèi)實(shí)例對(duì)象和數(shù)組分配內(nèi)存。這塊區(qū)域可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可,就像我們的磁盤(pán)空間一樣,在實(shí)現(xiàn)時(shí),既可以實(shí)現(xiàn)成固定大小的,也可以是擴(kuò)展的,如果是可擴(kuò)展的,則通過(guò)(-Xmx和-Xms控制),如果在隊(duì)中沒(méi)有內(nèi)存完成實(shí)例分配,并且堆也無(wú)法再擴(kuò)展時(shí),將會(huì)拋出OutOfMemoryError異常。
Java堆是垃圾回收器管理的主要區(qū)域,很多時(shí)候也被稱(chēng)為“GC”堆,在現(xiàn)在的實(shí)現(xiàn)上,堆被劃分成兩個(gè)不同的區(qū)域:新生代( Young )、老年代( Old );這也就是JVM采用的“分代收集算法”,簡(jiǎn)單說(shuō),就是針對(duì)不同特征的java對(duì)象采用不同的 策略實(shí)施存放和回收,自然所用分配機(jī)制和回收算法就不一樣。新生代( Young ) 又被劃分為三個(gè)區(qū)域:Eden、From Survivor、To Survivor。
方法區(qū)
方法區(qū)和Java堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域,用于存儲(chǔ)已被虛擬機(jī)加載的類(lèi)信息,常量、靜態(tài)變量,還包括在類(lèi)、實(shí)例、接口初始化時(shí)用到的特殊方法。虛擬機(jī)規(guī)范上把方法區(qū)描述為堆的一個(gè)邏輯部分,但是它卻有一個(gè)別名叫做“非堆”,目的就是與Java的堆區(qū)分開(kāi)來(lái)。
直接內(nèi)存直接內(nèi)存并不是JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分, 但也會(huì)被頻繁的使用: 在JDK 1.4引入的NIO提供了基于Channel與Buffer的IO方式, 它可以使用Native函數(shù)庫(kù)直接分配堆外內(nèi)存, 然后使用DirectByteBuffer對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作(詳見(jiàn): Java I/O 擴(kuò)展), 這樣就避免了在Java堆和Native堆中來(lái)回復(fù)制數(shù)據(jù), 因此在一些場(chǎng)景中可以顯著提高性能。顯然, 本機(jī)直接內(nèi)存的分配不會(huì)受到Java堆大小的限制(即不會(huì)遵守-Xms、-Xmx等設(shè)置), 但既然是內(nèi)存, 則肯定還是會(huì)受到本機(jī)總內(nèi)存大小及處理器尋址空間的限制, 因此動(dòng)態(tài)擴(kuò)展時(shí)也會(huì)出現(xiàn)OutOfMemoryError異常。
從例子來(lái)理解內(nèi)存模型這里我們引入一個(gè)比較簡(jiǎn)單的程序樣例,從具體的代碼角度去理解Jvm的內(nèi)存
一個(gè)person類(lèi)
public class Persion { private String name; public static String aninmal = 'dog' public Persion(String name){this.name = name } public String getName() {return name; } public void setName(String name) {this.name = name; } }
一個(gè)App類(lèi)
public class App { public static void main( String[] args ) {Persion persion1 = new Persion('張三'); Persion persion2 = new Persion('李四');persoion1.setName('王五'); } }
程序開(kāi)始運(yùn)行,系統(tǒng)啟動(dòng)了一個(gè)Java虛擬機(jī)進(jìn)程,Java虛擬機(jī)定位到方法區(qū)中App類(lèi)的main()方法的字節(jié)碼,開(kāi)始執(zhí)行它的指令。分別去創(chuàng)建Persion1和Persion2(這里我們以persion對(duì)象為跟蹤點(diǎn))
1、程序從main方法開(kāi)始執(zhí)行,既然提到了方法,根據(jù)上面的知識(shí),我們知道,它首先會(huì)在棧區(qū)動(dòng)工。在JAVA虛擬機(jī)進(jìn)程中,每個(gè)線程都會(huì)擁有一個(gè)方法調(diào)用棧,用來(lái)跟蹤線程運(yùn)行中一系列的方法調(diào)用過(guò)程,棧中的每一個(gè)元素就被稱(chēng)為棧幀,每當(dāng)線程調(diào)用一個(gè)方法的時(shí)候就會(huì)向方法棧壓入一個(gè)新幀。這里的幀用來(lái)存儲(chǔ)方法的參數(shù)、局部變量和運(yùn)算過(guò)程中的臨時(shí)數(shù)據(jù)。這時(shí)候執(zhí)行main方法的主線程會(huì)在棧區(qū)申請(qǐng)一片區(qū)域。根據(jù)源碼,它會(huì)識(shí)別出persion1和persion2分別為兩個(gè)變量,并且給它們定性是方法內(nèi)局部變量,因此,它被會(huì)添加到了執(zhí)行main()方法的主線程的JAVA方法調(diào)用棧中。
2、 接下來(lái)就是 “=” 賦值操作了,Java虛擬機(jī)接受運(yùn)行指令,發(fā)現(xiàn)右側(cè)是個(gè)對(duì)象實(shí)例,于是就直奔方法區(qū)而去,試圖找到Persion類(lèi)的類(lèi)型信息。首次運(yùn)行,發(fā)現(xiàn)并沒(méi)有找到Persion的信息,這時(shí)候Java虛擬機(jī)根據(jù)預(yù)設(shè)的規(guī)則,在無(wú)法找到類(lèi)信息的情況下,自行去加載Persion類(lèi),把Persion類(lèi)的類(lèi)型信息存放在方法區(qū)里。
3、 現(xiàn)在Persion類(lèi)的信息已經(jīng)被加載到了方法區(qū),這里Persion類(lèi)中的靜態(tài)變量animal也會(huì)被填充上值“dog”存放于方法區(qū),此時(shí)Java虛擬機(jī)根據(jù)我們代碼中的兩句new指令,分別去堆中劃出兩塊內(nèi)存區(qū)域,分別用于存放persion實(shí)例1和persion實(shí)例2,這兩個(gè)實(shí)例對(duì)象分別擁有自己獨(dú)立的內(nèi)存空間, 同時(shí)這倆實(shí)例持有著指向方法區(qū)的Persion類(lèi)的類(lèi)型信息的引用。這里所說(shuō)的引用,實(shí)際上指的是Persion類(lèi)的類(lèi)型信息在方法區(qū)中的內(nèi)存地址,其實(shí),就是有點(diǎn)類(lèi)似于C語(yǔ)言里的指針,而這個(gè)地址呢,就存放了在persion實(shí)例1、persopn實(shí)例2的數(shù)據(jù)區(qū)里。我們也能發(fā)現(xiàn)persion實(shí)例1和persion實(shí)例2共享animal這個(gè)變量,也就是說(shuō),無(wú)論使用哪一個(gè)引用(persion1和persion2)去修改這個(gè)animal變量,任何一個(gè)Persion對(duì)象使用這個(gè)變量的時(shí)候,都會(huì)發(fā)生改變。
4、到此為止已經(jīng)將main方法中的兩個(gè)成員變量persion1和persion2分別關(guān)聯(lián)到了堆中的對(duì)象。當(dāng)Java虛擬機(jī)執(zhí)行到persion1.setName()的時(shí)候,Java虛擬機(jī)根據(jù)main方法棧區(qū)中的persion1變量,定位到堆中的Persion名字為張三的實(shí)例(persion實(shí)例1),再根據(jù)這個(gè)實(shí)例所持有的類(lèi)信息引用(或者說(shuō)指針),定位到方法區(qū)的Persion類(lèi)信息,從中獲得setName(String name)方法,然后棧區(qū)再壓入一個(gè)新幀,并在其中完成參數(shù)(String name)的復(fù)制,然后根據(jù)指令,將堆中的Persion實(shí)例1 空間中的Name變成“王五”,然后結(jié)束
以上就是詳解Java的內(nèi)存模型的詳細(xì)內(nèi)容,更多關(guān)于Java的內(nèi)存模型的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!
相關(guān)文章:
1. asp批量添加修改刪除操作示例代碼2. ASP實(shí)現(xiàn)加法驗(yàn)證碼3. PHP循環(huán)與分支知識(shí)點(diǎn)梳理4. 讀大數(shù)據(jù)量的XML文件的讀取問(wèn)題5. 低版本IE正常運(yùn)行HTML5+CSS3網(wǎng)站的3種解決方案6. ASP刪除img標(biāo)簽的style屬性只保留src的正則函數(shù)7. JSP+Servlet實(shí)現(xiàn)文件上傳到服務(wù)器功能8. 解析原生JS getComputedStyle9. jsp+servlet實(shí)現(xiàn)猜數(shù)字游戲10. css代碼優(yōu)化的12個(gè)技巧
