欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
Java 虛擬機體系結構
眾所周知,Java源代碼被編譯器編譯成class文件。而并不是底層操作系統可以直接執行的二進(jìn)制指令(比如Windows OS的.exe文件)。因此,我們需要有一種平臺可以解釋class文件并運行它。而做到這一點(diǎn)的正是Java 虛擬機(JVM)。
實(shí)際上,JVM是一種解釋執行class文件的規范技術(shù)。各 個(gè)提 供商都可以根據規范,在不同的底層平臺上實(shí)現不同的JVM。
下面是JVM實(shí)現的基本結構框圖。其中類(lèi)裝載子系統、運行時(shí)數據區、執行引擎等 是JVM的必須要解決的幾大問(wèn)題。
★ 類(lèi)裝載器子系統
在JVM中,類(lèi)裝載器子系統負責查找并裝載Class文件。關(guān)于這部分的裝載細節詳見(jiàn)《JVM加載class文件的原理 》
★ 運行時(shí)數據區
當Java虛擬機運行一個(gè)程序時(shí),需要內存在存儲許多東西。比如字節碼,程序創(chuàng )建的對象,傳遞的方法參數,返回值,局部變量等等。JVM會(huì )把這些東西都組織到幾個(gè)“運行時(shí)數據區”中便于管理。
(1) 方法區
當JVM使用類(lèi)裝載器定位Class文件,并將其輸入到內存中時(shí)。會(huì )提取Class文件的類(lèi)型信息,并將這些信息存儲到方法區中。同時(shí)放入方法取中的還有該類(lèi)型中的類(lèi)靜態(tài)變量。下面我們具體看看需要存儲哪些信息?
●該類(lèi)型的全限定名。如java.io.FileOutputStream
●該類(lèi)型的直接超類(lèi)的全限定名。如java.io.OutputStream
●該類(lèi)型是類(lèi)類(lèi)型還是接口類(lèi)型。
●該類(lèi)型的訪(fǎng)問(wèn)修飾符(public、abstract、final)。
●任何直接超接口的全限定名的有序列表。如java.io.Closeable,  java.io.Flushable。
●該類(lèi)型的常量池。比如所有類(lèi)型、方法和字段的符號?;緮祿?lèi)型的直接數值等。
●字段信息。對類(lèi)型中聲明的每個(gè)字段,方法區中必須保存下面的信息。除此之外,這些字段在類(lèi)或者接口中的聲明順尋也必須保存。
字段名
字段的類(lèi)型
字段的修飾符(public, private, protected, static, final, volatile, transient的某個(gè)子集)
●方法信息。和字段一樣保存方法的相關(guān)信息。
方法名
方法的返回類(lèi)型
方法的參數的數量和類(lèi)型
方法的修飾符
方法的字節碼
操作數棧和棧幀中局部變量的大小 (見(jiàn)下面Java棧的內容)
異常表
●類(lèi)靜態(tài)變量。這里要注意:類(lèi)中的靜態(tài)變量時(shí)存放在方法區中的。并不是存放在堆中某一個(gè)該類(lèi)型的對象中的。 也就是我們常說(shuō)的“類(lèi)靜態(tài)變量屬于類(lèi),而不屬于對象”這句話(huà)的由來(lái)了。
●指向ClassLoader類(lèi)的引用。任何類(lèi)都需要被類(lèi)裝載器裝入內存。如果是被用戶(hù)自定義類(lèi)裝載器裝載的,那么JVM必須在類(lèi)型信息中存儲對該裝載器對象的引用。
●指向Class類(lèi)的 引用。對于每一個(gè)被裝載的類(lèi)型,虛擬機都會(huì )相應的為它創(chuàng )建一個(gè)java.lang.Class類(lèi)的實(shí)例,而且虛擬機還必須以某種方式把這個(gè)實(shí)例和存儲在方 法區中的類(lèi)型信息關(guān)聯(lián)起來(lái)。 這就使得我們可以在程序運行時(shí)查看某個(gè)加載進(jìn)內存的類(lèi)的當前狀態(tài)信息。也就是反射機制實(shí)現的根本。
●方法表。 為了能快速定位到類(lèi)型中的某個(gè)方法。JVM對每個(gè)裝載的類(lèi)型都會(huì )建立一個(gè)方法表,用于存儲該類(lèi)型對象可以調用的方法的直接引用,這些方法就包括從超類(lèi)中繼 承來(lái)的。而這張表與Java動(dòng)態(tài)綁定機制( 參見(jiàn)《java動(dòng)態(tài)綁定機制實(shí)現多態(tài) 》 )的實(shí)現是密切相關(guān)的。
方法區是多線(xiàn)程共享的。也就是當虛擬機實(shí)例開(kāi)始運行程序時(shí),邊運行邊加載 進(jìn)Class文 件。不同的Class文件都會(huì )提取出不同類(lèi)型信息存放在方法區中。同樣,方法區中不再需要運行的類(lèi)型信息會(huì )被垃圾回收線(xiàn)程丟棄掉。右圖形象的顯示出了方法區的樣子。
(2) 堆
Java 程序在運行時(shí)創(chuàng )建的所有類(lèi)型對象和數組都存儲在堆中。JVM會(huì )根據new指令在堆中開(kāi)辟一個(gè)確定類(lèi)型的對象內存空間。但是堆中開(kāi)辟對象的空間并沒(méi)有任何 人工 指令可以回收,而是通過(guò)JVM的垃圾回收器負責回收。
堆中對象存儲的是該對象以及對象所有超類(lèi)的實(shí)例數據(但不是靜態(tài)數據), 比如下面的類(lèi)型:
class X{
private int data;
private static int stcdata=0;
public X(int d){
this.data=d;
}
}
X x1=new X(100);
X x2=new X(200);
這樣在堆中開(kāi)辟了兩個(gè)對象x1和x2的內存空間。其中x1中的一個(gè)實(shí)例數據data=100,而x2的data=200。但是這兩個(gè)對象中都沒(méi)有stcdata這樣的數據,這個(gè)靜態(tài)數據存儲在上面講到的方法區中。
此外,堆中對象還必須有指向方法區中的類(lèi)信息數據(見(jiàn)上面方法區)。 為什么需要這個(gè)信息呢?因為當程序在運行時(shí)需要對象轉型,那么JVM必須檢查當前對象所屬類(lèi)型及父類(lèi)的信息。以判斷轉型是否是合法的,而這一點(diǎn)也是instanceof操作符實(shí)現的基礎。
當然,上述只是JVM的規范,具體堆的實(shí)現是由JVM設計者來(lái)決定。下面兩幅圖就直觀(guān)的表現出了堆對象的不同實(shí)現結構:
其中一個(gè)對象的引用可能在整個(gè)運行時(shí)數據區中的很多地方存在,比如Java棧,堆,方法區等。
堆中對象還應該關(guān)聯(lián)一個(gè)對象的鎖數據信息以及線(xiàn)程的等待集合。 這些都是實(shí)現Java線(xiàn)程同步機制的基礎。但實(shí)際上很多具體實(shí)現中并不在對象自身內部保存一個(gè)指向鎖數據的指針。而只有當第一次需要加鎖的時(shí)候才分配對應鎖數據。另外,每個(gè)對象都會(huì )從Object中繼承三個(gè)Object方法(wait、notify、notifyAll),當某個(gè)線(xiàn)程在一個(gè)對象上調用了等待方法時(shí)。JVM就會(huì )阻塞這個(gè)線(xiàn)程,并把這個(gè)線(xiàn)程放在該對象的等待集合中。知道另外一個(gè)線(xiàn)程在該對象上調用了notify/notifyAll,JVM才會(huì )在等待集合中喚醒一個(gè)或全部的等待線(xiàn)程(參見(jiàn)《正確理解線(xiàn)程等待和釋放(wait/notify)》)。
【數組對象】
在Java中,數組也是對象,那么自然在堆中會(huì )存儲數組的信息。事實(shí)也確實(shí)如此,對于JVM而言,數組與其他類(lèi)對象沒(méi)有任何區別。
數組也有屬于的類(lèi)Class,具有相同維度和類(lèi)型的數組都是同一個(gè)類(lèi)的實(shí)例,而不管數組的長(cháng)度是多少。
數組類(lèi)的名稱(chēng)由兩部分構成:(1)每一維用一個(gè)方括號“[”表示。(2) 用字符或字符串表示元素類(lèi)型。比如一維數組對象int[] a所屬類(lèi)型名為"[I",二維數組對象byte[] b所屬類(lèi)型名為"[[B"。
下圖是二維數組對象在堆中的具體實(shí)現方式:
(3) 程序計數器
對于一個(gè)運行的Java而言,每一個(gè)線(xiàn)程都有一個(gè)PC寄存器。當線(xiàn)程執行Java程序時(shí),PC寄存器的內容總是下一條將被執行的指令地址。
(4) Java棧 -  棧幀
每啟動(dòng)一個(gè)線(xiàn)程,JVM都會(huì )為它分配一個(gè)Java棧,用于存放方法中的局部變量,操作數以及異常數據等。當線(xiàn)程調用某個(gè)方法時(shí),JVM會(huì )根據方法區中該方法的字節碼組建一個(gè)棧幀。并將該棧幀壓入Java棧中,方法執行完畢時(shí),JVM會(huì )彈出該棧幀并釋放掉。
注意,Java棧中的數據是線(xiàn)程私有的,一個(gè)線(xiàn)程是無(wú)法訪(fǎng)問(wèn)另一個(gè)線(xiàn)程的Java棧的數據。這也就是為什么多線(xiàn)程編程時(shí),兩個(gè)相同線(xiàn)程執行同一方法時(shí),對方法內的局部變量時(shí)不需要數據同步的原因。
【棧幀 】
棧幀有三部分構成:局部變量區、操作數棧和幀數據區。在編譯器編譯Java代碼時(shí),就已經(jīng)在字節碼中為每個(gè)方法都設置好了局部變量區和操作數棧的數據和大小。并在JVM首次加載方法所屬的Class文件時(shí),就將這些數據放進(jìn)了方法區。因此在線(xiàn)程調用方法時(shí),只需要根據方法區中的局部變量區和操作數棧的大小來(lái)分配一個(gè)新的棧幀的內存大小,并堆入Java棧。
局部變量區: 用來(lái)存放方法中的所有局部變量值,包括傳遞的參數。這些數據會(huì )被組織成以一個(gè)字長(cháng)(32bit或64bit)為單位的數組結構(以索引0開(kāi)始)中。其中類(lèi)型為int, float, reference(引用類(lèi)型,記錄對象在堆中地址)和returnAddress(一種JVM內部使用的基本類(lèi)型)的值占用1個(gè)字長(cháng),而byte, char和shot會(huì )擴大成1個(gè)字長(cháng)存儲,long,double則使用2個(gè)字長(cháng)。
操作數棧: 用來(lái)在執行指令的時(shí)候存儲和使用中間結果數據。
幀數據區: 常量池的解析,正常方法返回以及異常派發(fā)機制的信息數據都存儲在其中。
下圖展示了addAndPrint()調用addTwoTypes()時(shí),Java棧的變化:
★ 執行引擎
運行Java的每一個(gè)線(xiàn)程都是一個(gè)獨立的虛擬機執行引擎的實(shí)例。從線(xiàn)程生命周期的開(kāi)始到結束,他要么在執行字節碼,要么在執行本地方法。一個(gè)線(xiàn)程可能通過(guò)解釋或者使用芯片級指令直接執行字節碼,或者間接通過(guò)JIT執行編譯過(guò)的本地代碼。
指令集: 實(shí)際上,Class文件中方法的字節碼流就是有JVM的指令序列構成的。每一條指令包含一個(gè)單字節的操作碼,后面跟隨0個(gè)或多個(gè)操作數。
Java虛擬機指令集關(guān)注的中心是操作數棧和局部變量集合。我們可以看看下面一組指令在執行引擎中執行的過(guò)程:
Jvm助記指令代碼 
 
iload_0    // 把存儲在局部變量區中索引為0的整數壓入操作數棧。
iload_1    // 把存儲在局部變量區中索引為1的整數壓入操作數棧。
iadd         // 從操作數棧中彈出兩個(gè)整數相加,在將結果壓入操作數棧。
istore_2   // 從操作數棧中彈出結果
很顯然,上面的指令反復用到了Java棧中的某一個(gè)方法棧幀。實(shí)際上執行引擎運行Java字節碼指令很多時(shí)候都是在不停的操作Java棧,也有的時(shí)候需要在堆中開(kāi)辟對象以及運行系統的本地指令等。但是Java棧的操作要比堆中的操作要快的多,因此反復開(kāi)辟對象是非常耗時(shí)的。這也是為什么Java程序優(yōu)化的時(shí)候,盡量減少new對象。
下面將會(huì )是很有趣的過(guò)程,我們用一段代碼來(lái)生動(dòng)的展現JVM是如何運行這段程序的。
通過(guò)編譯器將下面的代碼編譯成edu/hr/jvm/Test.class 和 edu/hr/jvm/bean/Act.class。然后開(kāi)始啟動(dòng)JVM:
Java代碼 
 
//源代碼 Test.java
package edu.hr.jvm;
import edu.hr.jvm.bean;
public class Test{
public static void main(String[] args){
Act act=new Act();
act.doMathForever();
}
}
//源代碼 Act.java
package edu.hr.jvm.bean;
public class Act{
public void doMathForever(){
int i=0;
for(;;){
i+=1;
i*=2;
}
}
}
(1) 首先OS會(huì )創(chuàng )建一個(gè)JVM實(shí)例(進(jìn)行必要的初始化工作,比如初始啟動(dòng)類(lèi)裝載器,初始運行時(shí)內存數據區等)。
(2) 然后通過(guò)自定義類(lèi)裝載器 加載Test.class。并提取Test.class字節碼中的信息存放在方法區 中(具體的信息在上面已經(jīng)講過(guò))。右圖展示了方法區中的Test類(lèi)信息,其中在常量池中有一個(gè)符號引用"Act"(注意:這個(gè)引用目前還沒(méi)有真正的類(lèi)信息 的內存地址)。
(3) 接著(zhù)JVM開(kāi)始從Test類(lèi)的main字節碼處開(kāi)始解釋執行。在運行之前,會(huì )在Java棧中組建一個(gè)main方法的棧幀 。如右圖Java棧所示。JVM需要運行任何方法前,通過(guò)在Java棧 中壓入一個(gè)幀棧。在這個(gè)幀棧的內存區域中進(jìn)行計算。
(4) 現在可以開(kāi)始執行main方法的第一條指令——JVM需要為常量池 的第一項的類(lèi)(符號引用Act)分配內存空間。但是Act類(lèi)此時(shí)還沒(méi)有加載進(jìn)JVM(因為常量池目前只有一個(gè)"Act"的符號引用)。
(5) JVM加載進(jìn)Act.class,并提取Act類(lèi)信息 放入方法區中。見(jiàn)上圖方法區所示,然后以一個(gè)直接指向方法區Act類(lèi)信息的直接引用替換開(kāi)始在常量池中的符號引用"Act",這個(gè)過(guò)程就是常量池解析 。以后就可以直接訪(fǎng)問(wèn)Act的類(lèi)信息了。
(6) 此時(shí)JVM可以根據方法區中的Act類(lèi)信息,在堆中開(kāi)辟一個(gè)Act類(lèi)對象 act。見(jiàn)上圖堆所示。
(7) 接著(zhù)開(kāi)始執行main方法中的第二條指令調用doMathForever。這個(gè)可以通過(guò)堆中act對象所指的方法表 中查找,然后定位到方法區中的Act類(lèi)信息中的doMathForever方法字節碼。在運行之前,仍然要組建一個(gè)doMathForever棧幀壓入Java棧,如上圖所示。(注意:JVM會(huì )根據方法區中doMathForever的字節碼來(lái)創(chuàng )建棧幀的局部變量區和操作數棧的大小)
(8) 接下來(lái)JVM開(kāi)始解釋運行Act.doMathForever字節碼的內容了。下面我們詳細的描述一下這個(gè)JVM的運行過(guò)程:
● 我們首先看一下doMathForever方法的字節碼在方法區中的指令如右圖,其中bytecode是指令的二進(jìn)制編碼 ,mnemonic是指令助記符 ,pc為程序計數器 (指向當前運行指令的下一條),offset為指令存放在方法區中的地址偏移 。
●然后在上面的圖Java棧中已經(jīng)顯示出了doMathForever方法的棧幀,其中比較重要的兩個(gè)部分是局部變量區和 操作數棧 。而此時(shí)在運行指令之前,局部變量區中只有一個(gè)整型i 的存儲位置(1個(gè)字長(cháng))。而操作數棧中還沒(méi)有被創(chuàng )建了2個(gè)字長(cháng)的大小(存儲大小是幀棧創(chuàng )建的時(shí)候由方法區中的數據確定的)。
局部變量區
index        hex value        value
(變量i)      0
optop: 0                 操作數棧
offset        hex value        value
optop->    0
1
●  下面運行每一條指令后,看一下局部變量區和操作數棧的變化:
① 指令[iconst_0]   將int類(lèi)型變量的數據0壓入操作數棧。
局部變量區                                                                  操作數棧
index        hex value        value                                 offset        hex value        value
(變量i)     0                                                                                  0           00000000          0
optop->    1
② 指令[istore_0]    彈出操作數棧頂的數據0,將結果存儲在局部變量區中index=0的空間中。
局部變量區                                                                  操作數棧
index        hex value        value                                 offset        hex value        value
(變量i)     0            00000000          0                          optop->   0
1
③指令[iinc 0 1]  把常量值1加到局部變量區中index=0的空間上。
局部變量區                                                                  操作數棧
index        hex value        value                                 offset        hex value        value
(變量i)     0            00000001          1                          optop->   0
1
④指令[iload_0]  把局部變量區index=0中的數據堆入操作數棧。
局部變量區                                                                  操作數棧
index        hex value        value                                 offset        hex value        value
(變量i)     0            00000001          1                                          0         00000001          1
optop->   1
⑤指令[iconst_2] 把int類(lèi)型變量的數據2壓入操作數棧。
局部變量區                                                                  操作數棧
index        hex value        value                                 offset        hex value        value
(變量i)     0            00000001          1                                          0         00000001          1
1         00000002           2
optop->
⑥指令[imul]  彈出操作數棧中的兩個(gè)數據1和2,相乘之后的結果2堆入操作數棧
局部變量區                                                                  操作數棧
index        hex value        value                                 offset        hex value        value
(變量i)     0            00000001          1                           optop->   0         00000002           2
1
⑦指令[istore_0]  彈出操作數棧頂的數據2,將結果存儲在局部變量區中index=0的空間中。
局部變量區                                                                  操作數棧
index        hex value        value                                 offset        hex value        value
(變量i)     0            00000002          2                           optop->   0
1
⑧指令[goto 2]   跳轉到指令iinc 0 1處循環(huán)執行下去.....
當然,這個(gè)例子不停的執行下去只會(huì )出現算術(shù)溢出,也就是一個(gè)字長(cháng)(2bytes)的整型變量i 無(wú)法表示不停計算的結果了。但是JVM不會(huì )拋出任何異常,
附:在《深入Java虛擬機》一書(shū)第5章節有一個(gè)JVM模擬運行上面程序的源代碼和applet展示,做的很不錯。下面是這本書(shū)的配到源代碼,大家可以學(xué)習一下。
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
操作系統內存與JVM內存
JVM內存模型
Java中JVM虛擬機詳解
資深架構師技術(shù)分享:JVM內存結構與內存模型
2萬(wàn)字長(cháng)文包教包會(huì ) JVM 內存結構 保姆級學(xué)習筆記
簡(jiǎn)述JVM(1)——類(lèi)加載器和運行時(shí)數據區
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久