新聞中心
了解JVM的結(jié)構(gòu),好在面試時(shí)吹牛
作者:李新杰 2019-12-23 15:17:21
云計(jì)算
虛擬化 在一個(gè)線程執(zhí)行的任何時(shí)刻,都只會(huì)有一個(gè)幀是處于激活的。這個(gè)幀被稱為當(dāng)前幀,與之對(duì)應(yīng)的方法被稱為當(dāng)前方法,方法所在的類被稱為當(dāng)前類,此時(shí)用到的本地變量數(shù)組和操作數(shù)棧也都是當(dāng)前幀的。

創(chuàng)新互聯(lián)公司從2013年創(chuàng)立,先為平魯?shù)确?wù)建站,平魯?shù)鹊仄髽I(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為平魯企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。
jvm包括兩種數(shù)據(jù)類型,基本類型和引用類型。
基本類型包括,數(shù)值類型,boolean類型,和returnAddress類型。
數(shù)值類型包括,整型,浮點(diǎn)型,和char類型。
boolean類型同樣只有true和false。
returnAddress類型是一個(gè)指針,指向jvm指令的操作碼,在Java中沒有與之對(duì)應(yīng)的類型。
boolean類型的操作會(huì)被轉(zhuǎn)化為int類型的操作進(jìn)行,boolean數(shù)組會(huì)當(dāng)成byte數(shù)組去操作。1表示true,0表示false。
引用類型包括三種,類類型,數(shù)組類型,和接口類型。
它們的值是動(dòng)態(tài)創(chuàng)建的類實(shí)例,數(shù)組,或?qū)崿F(xiàn)接口的類實(shí)例。
數(shù)組有component類型和element類型,component類型就是數(shù)組去掉最外層維度后剩下的類型,可能還是一個(gè)數(shù)組類型(對(duì)于多維數(shù)組)。
element類型就是數(shù)組里面存儲(chǔ)的最小數(shù)據(jù)的類型,它必須是一個(gè)基本類型,類類型,或接口類型。
對(duì)于一維數(shù)組的話,component類型和element類型是相同的。
引用類型還有一個(gè)特殊值,就是null,表示沒有引用任何對(duì)象。
運(yùn)行時(shí)公有數(shù)據(jù)區(qū)
堆
jvm有一個(gè)堆,在所有jvm線程間共享,堆是一個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)域,所有為類實(shí)例和數(shù)組分配的內(nèi)存都來自于它。
堆在jvm啟動(dòng)時(shí)創(chuàng)建,堆中對(duì)象不用顯式釋放,gc會(huì)幫我們釋放并回收內(nèi)存。
方法區(qū)
jvm有一個(gè)方法區(qū),在所有jvm線程間共享,它存儲(chǔ)每一個(gè)類的結(jié)構(gòu)。
像運(yùn)行時(shí)常量池,字段和方法數(shù)據(jù),方法和構(gòu)造函數(shù)的代碼,還有特殊的方法用于類和實(shí)例的初始化,以及接口的初始化。
方法區(qū)在jvm啟動(dòng)時(shí)創(chuàng)建,雖然方法區(qū)在邏輯上是堆的一部分。
但簡(jiǎn)單實(shí)現(xiàn)時(shí)可以選擇不進(jìn)行g(shù)c和壓縮,本規(guī)范沒有強(qiáng)制要求方法區(qū)的位置,也沒有要求管理已編譯代碼的策略。
運(yùn)行時(shí)常量池
運(yùn)行時(shí)常量池就是類或接口的字節(jié)碼文件里的常量池的運(yùn)行時(shí)表示形式,它包含幾種常量。
如在編譯時(shí)就已經(jīng)知道的數(shù)字字面量值,和必須在運(yùn)行時(shí)解析的方法和字段的引用,運(yùn)行時(shí)常量池的功能類似于傳統(tǒng)語言的符號(hào)表,不過它包含的數(shù)據(jù)會(huì)更加寬泛。
運(yùn)行時(shí)常量池分配在jvm的方法區(qū),類或接口的運(yùn)行時(shí)常量池在類或接口被jvm創(chuàng)建時(shí)才會(huì)構(gòu)建。
運(yùn)行時(shí)私有數(shù)據(jù)區(qū)
pc寄存器
jvm支持一次運(yùn)行多個(gè)線程,每個(gè)線程都有自己的pc寄存器,任何時(shí)候一個(gè)線程只能運(yùn)行一個(gè)方法的代碼。
如果方法不是native的,pc寄存器包含當(dāng)前正在被執(zhí)行的jvm指令地址,如果方法是native的,pc寄存器的值是未定義的。
jvm棧
每一個(gè)jvm線程都有一個(gè)私有的jvm棧,隨著線程的創(chuàng)建而創(chuàng)建,棧中存儲(chǔ)的是幀。
jvm棧和傳統(tǒng)語言如C的棧相似,保存局部變量和部分計(jì)算結(jié)果,參與方法的調(diào)用和返回。jvm棧主要用于幀的出棧和入棧,除此之外沒有其它操作,
幀可能是在堆上分配的,所以jvm棧使用的內(nèi)存不必是連續(xù)的。
native方法棧
native方法不是用Java語言寫的,為了支持它需要使用傳統(tǒng)棧,如C語言棧。不過jvm不能加載native方法,所以也不需要提供native方法需要的棧。
幀
每次當(dāng)一個(gè)方法被調(diào)用時(shí)一個(gè)新的幀會(huì)被創(chuàng)建。當(dāng)方法調(diào)用完成時(shí),與之對(duì)應(yīng)的幀會(huì)被銷毀,無論是正常完成還是拋異常結(jié)束。
所以幀是方法調(diào)用的具體體現(xiàn)形式,或稱方法調(diào)用是以幀的形式進(jìn)行的。幀用來存儲(chǔ)數(shù)據(jù)和部分計(jì)算結(jié)果,和執(zhí)行動(dòng)態(tài)鏈接,方法返回值,分發(fā)異常。
幀分配在創(chuàng)建幀的線程的jvm棧上,每一個(gè)幀都有自己的本地變量數(shù)組,自己的操作數(shù)據(jù)棧,和一個(gè)對(duì)當(dāng)前方法所在類的運(yùn)行時(shí)常量池的引用。
本地變量數(shù)組和操作數(shù)棧的大小在編譯時(shí)就確定了,它們隨著和幀關(guān)聯(lián)的方法編譯后的代碼一起被提供,因此幀這種數(shù)據(jù)結(jié)構(gòu)的大小只依賴于jvm的實(shí)現(xiàn),這些結(jié)構(gòu)所需的內(nèi)存可以在方法調(diào)用時(shí)同時(shí)被分配。
在一個(gè)線程執(zhí)行的任何時(shí)刻,都只會(huì)有一個(gè)幀是處于激活的。這個(gè)幀被稱為當(dāng)前幀,與之對(duì)應(yīng)的方法被稱為當(dāng)前方法,方法所在的類被稱為當(dāng)前類,此時(shí)用到的本地變量數(shù)組和操作數(shù)棧也都是當(dāng)前幀的。
一個(gè)幀將不在繼續(xù)是當(dāng)前幀,如果它的方法調(diào)用了另一個(gè)方法,或者它的方法結(jié)束了。
當(dāng)一個(gè)方法被調(diào)用,一個(gè)新的幀被創(chuàng)建,當(dāng)執(zhí)行控制由原來的方法傳遞到新的方法時(shí),這個(gè)新的幀變?yōu)楫?dāng)前幀。
當(dāng)方法返回時(shí),當(dāng)前幀把方法執(zhí)行的結(jié)果傳回到上一幀,當(dāng)上一幀被激活的同時(shí)當(dāng)前幀會(huì)被丟棄。
本地變量數(shù)組
每一幀都包含一個(gè)變量數(shù)組,就是都熟知的本地變量存儲(chǔ)的地方。這個(gè)本地變量數(shù)組的長(zhǎng)度在編譯時(shí)確定,隨著編譯后的方法代碼一起提供。
通常一個(gè)本地變量(的位置)能夠存儲(chǔ)一個(gè)類型的值,但是long和double類型卻需要兩個(gè)本地變量(的位置)才能存一個(gè)值。
本地變量按索引尋址,第一個(gè)本地變量的索引是0。long和double需要消耗兩個(gè)連續(xù)的索引,但卻是按照較小的這個(gè)索引尋址的。不能按照較大的那個(gè)索引去讀數(shù)據(jù),但是可以寫入,當(dāng)然這樣將使本地變量?jī)?nèi)容錯(cuò)亂。
在方法被調(diào)用時(shí),jvm使用本地變量來接收傳遞進(jìn)來的參數(shù)值。在類(靜態(tài))方法調(diào)用時(shí),所有參數(shù)被傳入從索引0開始的連貫的本地變量數(shù)組里。
在實(shí)例(非靜態(tài))方法調(diào)用時(shí),索引0處總是傳入正在其上執(zhí)行方法調(diào)用的那個(gè)對(duì)象的引用,(就是Java中的this了),所有參數(shù)被傳入從1開始的連貫的本地變量數(shù)組里。
操作數(shù)棧
每個(gè)幀包含一個(gè)后進(jìn)先出的棧,用于存儲(chǔ)正在執(zhí)行的jvm指令的操作數(shù),就是都熟知的操作數(shù)棧,這個(gè)棧的最大深度在編譯時(shí)就已確定,隨著編譯后的方法代碼一起提供。
當(dāng)幀被創(chuàng)建時(shí),操作數(shù)棧是空的,jvm提供一些指令用于加載常量值,本地變量值,字段值到操作數(shù)棧上,另一些jvm指令采用操作數(shù)棧上的操作數(shù)進(jìn)行操作,并把結(jié)果放回到操作數(shù)棧上。
操作數(shù)棧也用于準(zhǔn)備將要傳遞給方法調(diào)用的參數(shù)和接收方法調(diào)用返回的結(jié)果。
long和double類型的值占用兩個(gè)單位的棧深度,其它類型的值占用一個(gè)單位的棧深度。
動(dòng)態(tài)鏈接
每一個(gè)幀都包含了對(duì)當(dāng)前方法所屬類型的運(yùn)行時(shí)常量池的引用。目的是為了支持方法代碼的動(dòng)態(tài)鏈接。class文件中描述一個(gè)方法引用被調(diào)用的方法和被訪問的變量的代碼,是采用符號(hào)引用的形式實(shí)現(xiàn)的。
符號(hào)引用的形式可以粗略的認(rèn)為是字符串的形式,就是用字符串標(biāo)明需要調(diào)用哪個(gè)類的哪個(gè)方法或訪問哪個(gè)字段或變量。就像符號(hào)引用這個(gè)名字一樣,這些僅僅是符號(hào),是拿不到具體值的,所以必須要進(jìn)行轉(zhuǎn)換。
動(dòng)態(tài)鏈接就是把這些符號(hào)方法引用轉(zhuǎn)換為具體的方法引用,在必要時(shí)加載類來解析尚未明確的符號(hào),把符號(hào)變量的訪問轉(zhuǎn)換為這些變量運(yùn)行時(shí)所在存儲(chǔ)結(jié)構(gòu)的適合的偏移量(索引)。這樣的方式又稱為后期綁定。
方法調(diào)用
一個(gè)方法調(diào)用正常完成(即沒有拋異常)時(shí),會(huì)根據(jù)所返回的值的類型執(zhí)行一個(gè)適合的return指令,當(dāng)前幀會(huì)去恢復(fù)調(diào)用者的狀態(tài),包括它的本地變量和操作數(shù)棧,使調(diào)用者的程序計(jì)數(shù)器適合的遞增來跳過剛剛的那個(gè)方法調(diào)用指令。
返回值會(huì)被放到調(diào)用者幀的操作數(shù)棧上,然后繼續(xù)執(zhí)行調(diào)用者方法的幀。
一個(gè)方法在調(diào)用時(shí)拋出了異常,且這個(gè)異常沒有在這個(gè)方法內(nèi)被捕獲處理,將會(huì)導(dǎo)致這個(gè)方法調(diào)用的突然結(jié)束,這種情況下永遠(yuǎn)不會(huì)向方法的調(diào)用者返回一個(gè)值。
特殊方法
站在jvm的級(jí)別,每一個(gè)用Java寫的構(gòu)造函數(shù)都以一個(gè)實(shí)例初始化方法出現(xiàn),且都是特殊的名字,就是 ,這個(gè)名字是編譯器提供的。
實(shí)例初始化方法只能在jvm內(nèi)部使用invokespecial這個(gè)指令調(diào)用,且只能在尚未初始化的類實(shí)例上調(diào)用。
一個(gè)類或接口最多可以有一個(gè)類或接口初始化方法,通過調(diào)用這個(gè)方法被初始化。類或接口的初始化方法也有特殊的名字,就是 ,該方法沒有參數(shù),且返回值是void。
方法名稱也是由編譯器提供的,從Java7開始,在字節(jié)碼中這個(gè)方法必須被標(biāo)記為靜態(tài)的才行。
這個(gè)初始化方法是被jvm隱式調(diào)用的,它們絕對(duì)不會(huì)直接被用任何jvm指令調(diào)用,僅作為類初始化進(jìn)程的一部分被間接的調(diào)用。
Java類庫
jvm必須為Java類庫的實(shí)現(xiàn)提供足夠的支持。一些類庫中的類如果沒有jvm協(xié)助是無法實(shí)現(xiàn)的。
反射,就是在運(yùn)行時(shí)獲取某個(gè)類的類型相關(guān)信息,如它的字段信息,方法信息,構(gòu)造函數(shù)信息,父類信息,實(shí)現(xiàn)的接口信息。
這些信息都必須是把一個(gè)類加載完之后才可以知道的,只有jvm才可以加載類。如java.lang.reflect這個(gè)包下的類和Class這個(gè)類。
在Java中加載一個(gè)類或接口用類加載器,即ClassLoader,背后還是委托給jvm來實(shí)現(xiàn)的。
鏈接和初始化一個(gè)類或接口。
安全,如java.security包下的類,還有其它類像SecurityManager。
多線程,如線程這個(gè)類Thread。
弱引用,像java.lang.ref包下的類。
公有設(shè)計(jì),私有實(shí)現(xiàn)
以上內(nèi)容只是jvm的一個(gè)“相對(duì)寬泛”的規(guī)范,它并不是實(shí)現(xiàn)方案,也不是實(shí)現(xiàn)細(xì)節(jié)。
實(shí)現(xiàn)者可以根據(jù)自身的需要來實(shí)現(xiàn)jvm,如運(yùn)行在后端服務(wù)器上的jvm和運(yùn)行在移動(dòng)設(shè)備上的jvm肯定側(cè)重點(diǎn)有所不同。
從事Java的人都知道,事實(shí)上jvm是有較多的實(shí)現(xiàn)版本。
由于jvm是處在Java語言和操作系統(tǒng)之間的,所以它要向上提供對(duì)Java的支持,向下與操作系統(tǒng)良好交互。
寫在最后
高級(jí)語言(Java,C#)中的很多操作如文件操作,網(wǎng)絡(luò)操作,內(nèi)存操作,線程操作,I/O操作等,都不是高級(jí)語言自身能夠?qū)崿F(xiàn)的。
也不是它們的虛擬機(jī)(JVM,CLR)能夠?qū)崿F(xiàn)的,實(shí)際最終是由操作系統(tǒng)實(shí)現(xiàn)的,因?yàn)檫@些都是系統(tǒng)資源,只有操作系統(tǒng)才有權(quán)限訪問。
如果你用Java或C#代碼創(chuàng)建了一個(gè)文件,千萬不要以為是Java或C#創(chuàng)建了這個(gè)文件,它們只是層層向下調(diào)用了操作系統(tǒng)的API,然后到文件系統(tǒng)API,最后可能到磁盤驅(qū)動(dòng)程序。
由此可以看出,要想設(shè)計(jì)一門語言,不單單是關(guān)鍵字、語法、編譯器,類庫,虛擬機(jī)這些,還要深度了解操作系統(tǒng),甚至是硬件,如CPU架構(gòu)和CPU指令集等。
所以,和語言相關(guān)的事情,每一項(xiàng)都是異常的繁瑣復(fù)雜,都需要投入大量的人力、財(cái)力、時(shí)間去研究,最后即使研究成功了,可能沒有生態(tài),沒人使用,自然也無法賺錢。
因此,國(guó)人現(xiàn)在還沒有一門屬于自己的真正語言。
分享題目:了解JVM的結(jié)構(gòu),好在面試時(shí)吹牛
當(dāng)前地址:http://fisionsoft.com.cn/article/copjdcg.html


咨詢
建站咨詢
