天天看點

java虛拟機,jvm 原理,工作原理,類加載原理---jvm系列整理

jvm工作原理

Java虛拟機

一、什麼是Java虛拟機

Java虛拟機是一個想象中的機器,在實際的計算機上通過軟體模拟來實作。Java虛拟機有自己想象中的硬體,如處理器、堆棧、寄存器等,還具有相應的指令系統。

1.為什麼要使用Java虛拟機

Java語言的一個非常重要的特點就是與平台的無關性。而使用Java虛拟機是實作這一特點的關鍵。一般的進階語言如果要在不同的平台上運作,至少需要編譯成不同的目标代碼。而引入Java語言虛拟機後,Java語言在不同平台上運作時不需要重新編譯。Java語言使用模式Java虛拟機屏蔽了與具體平台相關的資訊,使得Java語言編譯程式隻需生成在Java虛拟機上運作的目标代碼(位元組碼),就可以在多種平台上不加修改地運作。Java虛拟機在執行位元組碼時,把位元組碼解釋成具體平台上的機器指令執行。

2.誰需要了解Java虛拟機

Java虛拟機是Java語言底層實作的基礎,對Java語言感興趣的人都應對Java虛拟機有個大概的了解。這有助于了解Java語言的一些性質,也有助于使用Java語言。對于要在特定平台上實作Java虛拟機的軟體人員,Java語言的編譯器作者以及要用硬體晶片實作Java虛拟機的人來說,則必須深刻了解Java虛拟機的規範。另外,如果你想擴充Java語言,或是把其它語言編譯成Java語言的位元組碼,你也需要深入地了解Java虛拟機。

3.Java虛拟機支援的資料類型

Java虛拟機支援Java語言的基本資料類型如下:

byte://1位元組有符号整數的補碼

short://2位元組有符号整數的補碼

int://4位元組有符号整數的補碼

long://8位元組有符号整數的補碼

float://4位元組IEEE754單精度浮點數

double://8位元組IEEE754雙精度浮點數

char://2位元組無符号Unicode字元

幾乎所有的Java類型檢查都是在編譯時完成的。上面列出的原始資料類型的資料在Java執行時不需要用硬體标記。*作這些原始資料類型資料的位元組碼(指令)本身就已經指出了*作數的資料類型,例如iadd、ladd、fadd和dadd指令都是把兩個數相加,其*作數類型别是int、long、 float和double。虛拟機沒有給boolean(布爾)類型設定單獨的指令。boolean型的資料是由integer指令,包括integer 傳回來處理的。boolean型的數組則是用byte數組來處理的。虛拟機使用IEEE754格式的浮點數。不支援IEEE格式的較舊的計算機,在運作 Java數值計算程式時,可能會非常慢。

虛拟機支援的其它資料類型包括:

object//對一個Javaobject(對象)的4位元組引用

returnAddress//4位元組,用于jsr/ret/jsr-w/ret-w指令

注:Java數組被當作object處理。

虛拟機的規範對于object内部的結構沒有任何特殊的要求。在Sun公司的實作中,對object的引用是一個句柄,其中包含一對指針:一個指針指向該object的方法表,另一個指向該object的資料。用Java虛拟機的位元組碼表示的程式應該遵守類型規定。Java虛拟機的實作應拒絕執行違反了類型規定的位元組碼程式。Java虛拟機由于位元組碼定義的限制似乎隻能運作于32位位址空間的機器上。但是可以建立一個Java虛拟機,它自動地把位元組碼轉換成64位的形式。從Java虛拟機支援的資料類型可以看出,Java對資料類型的内部格式進行了嚴格規定,這樣使得各種Java虛拟機的實作對資料的解釋是相同的,進而保證了Java的與平台無關性和可

移植性。

二、Java虛拟機體系結構

Java虛拟機由五個部分組成:一組指令集、一組寄存器、一個棧、一個無用單元收集堆(Garbage-collected-heap)、一個方法區域。這五部分是Java虛拟機的邏輯成份,不依賴任何實作技術或組織方式,但它們的功能必須在真實機器上以某種方式實作。

1.Java指令集

Java虛拟機支援大約248個位元組碼。每個位元組碼執行一種基本的CPU運算,例如,把一個整數加到寄存器,子程式轉移等。Java指令集相當于Java程式的彙編語言。

Java指令集中的指令包含一個單位元組的*作符,用于指定要執行的*作,還有0個或多個*作數,提供*作所需的參數或資料。許多指令沒有*作數,僅由一個單位元組的*作符構成。 虛拟機的内層循環的執行過程如下:

do{

取一個*作符位元組;

根據*作符的值執行一個動作;

}while(程式未結束)

由于指令系統的簡單性,使得虛拟機執行的過程十分簡單,進而有利于提高執行的效率。指令中*作數的數量和大小是由*作符決定的。如果*作數比一個位元組大,那麼它存儲的順序是高位位元組優先。例如,一個16位的參數存放時占用兩個位元組,其值為:

第一個位元組*256+第二個位元組位元組碼指令流一般隻是位元組對齊的。指令tableswitch和lookup是例外,在這兩條指令内部要求強制的4位元組邊界對齊。

2.寄存器

Java虛拟機的寄存器用于儲存機器的運作狀态,與微處理器中的某些專用寄存器類似。

Java虛拟機的寄存器有四種:

pc:Java程式計數器。

optop:指向*作數棧頂端的指針。

frame:指向目前執行方法的執行環境的指針。

vars:指向目前執行方法的局部變量區第一個變量的指針。

Java虛拟機

Java虛拟機是棧式的,它不定義或使用寄存器來傳遞或接受參數,其目的是為了保證指令集的簡潔性和實作時的高效性(特别是對于寄存器數目不多的處理器)。

所有寄存器都是32位的。

3.棧

Java虛拟機的棧有三個區域:局部變量區、運作環境區、*作數區。

(1)局部變量區 每個Java方法使用一個固定大小的局部變量集。它們按照與vars寄存器的字偏移量來尋址。局部變量都是32位的。長整數和雙精度浮點數占據了兩個局部變量的空間,卻按照第一個局部變量的索引來尋址。(例如,一個具有索引n的局部變量,如果是一個雙精度浮點數,那麼它實際占據了索引n和n+1所代表的存儲空間。)虛拟機規範并不要求在局部變量中的64位的值是64位對齊的。虛拟機提供了把局部變量中的值裝載到*作數棧的指令, 也提供了把*作數棧中的值寫入局部變量的指令。

(2)運作環境區 在運作環境中包含的資訊用于動态連結,正常的方法傳回以及異常傳播。

·動态連結

運作環境包括對指向目前類和目前方法的解釋器符号表的指針,用于支援方法代碼的動态連結。方法的class檔案代碼在引用要調用的方法和要通路的變量時使用符号。動态連結把符号形式的方法調用翻譯成實際方法調用,裝載必要的類以解釋還沒有定義的符号,并把變量通路翻譯成與這些變量運作時的存儲結構相應的偏移位址。動态連結方法和變量使得方法中使用的其它類的變化不會影響到本程式的代碼。

·正常的方法傳回

如果目前方法正常地結束了,在執行了一條具有正确類型的傳回指令時,調用的方法會得到一個傳回值。執行環境在正常傳回的情況下用于恢複調用者的寄存器,并把調用者的程式計數器增加一個恰當的數值,以跳過已執行過的方法調用指令,然後在調用者的執行環境中繼續執行下去。

·異常和錯誤傳播

異常情況在Java中被稱作Error(錯誤)或Exception(異常),是Throwable類的子類,在程式中的原因是:①動态連結錯,如無法找到所需的class檔案。②運作時錯,如對一個空指針的引用

·程式使用了throw語句。

當異常發生時,Java虛拟機采取如下措施:

·檢查與目前方法相聯系的catch子句表。每個catch子句包含其有效指令範圍,能夠處理的異常類型,以及處理異常的代碼塊位址。

·與異常相比對的catch子句應該符合下面的條件:造成異常的指令在其指令範圍之内,發生的異常類型是其能處理的異常類型的子類型。如果找到了比對的 catch子句,那麼系統轉移到指定的異常處理塊處執行;如果沒有找到異常處理塊,重複尋找比對的catch子句的過程,直到目前方法的所有嵌套的 catch子句都被檢查過。

·由于虛拟機從第一個比對的catch子句處繼續執行,是以catch子句表中的順序是很重要的。因為Java代碼是結構化的,是以總可以把某個方法的所有的異常處理器都按序排列到一個表中,對任意可能的程式計數器的值,都可以用線性的順序找到合适的異常處理塊,以處理在該程式計數器值下發生的異常情況。

·如果找不到比對的catch子句,那麼目前方法得到一個"未截獲異常"的結果并傳回到目前方法的調用者,好像異常剛剛在其調用者中發生一樣。如果在調用者中仍然沒有找到相應的異常處理塊,那麼這種錯誤傳播将被繼續下去。如果錯誤被傳播到最頂層,那麼系統将調用一個預設的異常處理塊。

(3)*作數棧區 機器指令隻從*作數棧中取*作數,對它們進行*作,并把結果傳回到棧中。選擇棧結構的原因是:在隻有少量寄存器或非通用寄存器的機器 (如Intel486)上,也能夠高效地模拟虛拟機的行為。*作數棧是32位的。它用于給方法傳遞參數,并從方法接收結果,也用于支援*作的參數,并儲存 *作的結果。例如,iadd指令将兩個整數相加。相加的兩個整數應該是*作數棧頂的兩個字。這兩個字是由先前的指令壓進堆棧的。這兩個整數将從堆棧彈出、相加,并把結果壓回到*作數棧中。

每個原始資料類型都有專門的指令對它們進行必須的*作。每個*作數在棧中需要一個存儲位置,除了long和double型,它們需要兩個位置。* 作數隻能被适用于其類型的*作符所*作。例如,壓入兩個int類型的數,如果把它們當作是一個long類型的數則是非法的。在Sun的虛拟機實作中,這個限制由位元組碼驗證器強制實行。但是,有少數*作(*作符dupe和swap),用于對運作時資料區進行*作時是不考慮類型的。

4.無用單元收集堆

Java的堆是一個運作時資料區,類的執行個體(對象)從中配置設定空間。Java語言具有無用單元收集能力:它不給程式員顯式釋放對象的能力。Java不規定具體使用的無用單元收集算法,可以根據系統的需求使用各種各樣的算法。

5.方法區

方法區與傳統語言中的編譯後代碼或是Unix程序中的正文段類似。它儲存方法代碼(編譯後的java代碼)和符号表。在目前的Java實作中,方法代碼不包括在無用單元收集堆中,但計劃在将來的版本中實作。每個類檔案包含了一個Java類或一個Java界面的編譯後的代碼。可以說類檔案是Java 語言的執行代碼檔案。為了保證類檔案的平台無關性,Java虛拟機規範中對類檔案的格式也作了詳細的說明。其具體細節請參考Sun公司的Java虛拟機規範。

注:文中的* 是“操”的意思

jvm載入原理作業系統裝入jvm是通過jdk中java.exe來完成,通過下面4步來完成jvm環境.

1.建立jvm裝載環境和配置

2.裝載jvm.dll

3.初始化jvm.dll并挂界到JNIENV(JNI調用接口)執行個體

4.調用JNIEnv執行個體裝載并處理class類。

在我們運作和調試java程式的時候,經常會提到一個jvm的概念.jvm是java程式運作的環境,但是他同時一個作業系統的一個應用程式一個程序,是以他也有他自己的運作的生命周期,也有自己的代碼和資料空間.

首先來說一下jdk這個東西,不管你是初學者還是高手,是j2ee程式員還是j2se程式員,jdk總是在幫我們做一些事情.我們在了解java之前首先大師們會給我們提供說jdk這個東西.它在java整個體系中充當着什麼角色呢?我很驚歎sun大師們設計天才,能把一個如此完整的體系結構化的如此完美.jdk在這個體系中充當一個生産加工中心,産生所有的資料輸出,是所有指令和戰略的執行中心.本身它提供了java的完整方案,可以開發目前java能支援的所有應用和系統程式.這裡說一個問題,大家會問,那為什麼還有j2me,j2ee這些東西,這兩個東西目的很簡單,分别用來簡化各自領域内的開發和建構過程.jdk除了jvm之外,還有一些核心的API,內建API,使用者工具,開發技術,開發工具和API等組成

好了,廢話說了那麼多,來點于主題相關的東西吧.jvm在整個jdk中處于最底層,負責于作業系統的互動,用來屏蔽作業系統環境,提供一個完整的java運作環境,是以也就虛拟計算機. 作業系統裝入jvm是通過jdk中java.exe來完成,通過下面4步來完成jvm環境.

1.建立jvm裝載環境和配置

2.裝載jvm.dll

3.初始化jvm.dll并挂界到JNIENV(JNI調用接口)執行個體

4.調用JNIEnv執行個體裝載并處理class類。

一.jvm裝入環境,jvm提供的方式是作業系統的動态連接配接檔案.既然是檔案那就一個裝入路徑的問題,java是怎麼找這個路徑的呢?當你在調用java test的時候,作業系統會在path下在你的java.exe程式,java.exe就通過下面一個過程來确定jvm的路徑和相關的參數配置了.下面基于windows的實作的分析.

  首先查找jre路徑,java是通過GetApplicationHome api來獲得目前的java.exe絕對路徑,c:/j2sdk1.4.2_09/bin/java.exe,那麼它會截取到絕對路徑c:/j2sdk1.4.2_09/,判斷c:/j2sdk1.4.2_09/bin/java.dll檔案是否存在,如果存在就把c:/j2sdk1.4.2_09/作為jre路徑,如果不存在則判斷c:/j2sdk1.4.2_09/jre/bin/java.dll是否存在,如果存在這c:/j2sdk1.4.2_09/jre作為jre路徑.如果不存在調用GetPublicJREHome查HKEY_LOCAL_MACHINE/Software/JavaSoft/Java Runtime Environment/“目前JRE版本号”/JavaHome的路徑為jre路徑。

  然後裝載jvm.cfg檔案JRE路徑+/lib+/ARCH(CPU構架)+/jvm.cfgARCH(CPU構架)的判斷是通過java_md.c中GetArch函數判斷的,該函數中windows平台隻有兩種情況:WIN64的‘ia64’,其他情況都為‘i386’。以我的為例:C:/j2sdk1.4.2_09/jre/lib/i386/jvm.cfg.主要的内容如下:

-client KNOWN

-server KNOWN

-hotspot ALIASED_TO -client

-classic WARN

-native ERROR

-green ERROR

在我們的jdk目錄中jre/bin/server和jre/bin/client都有jvm.dll檔案存在,而java正是通過jvm.cfg配置檔案來管理這些不同版本的jvm.dll的.通過檔案我們可以定義目前jdk中支援那些jvm,前面部分(client)是jvm名稱,後面是參數,KNOWN表示jvm存在,ALIASED_TO表示給别的jvm取一個别名,WARN表示不存在時找一個jvm替代,ERROR表示不存在抛出異常.在運作java XXX是,java.exe會通過CheckJvmType來檢查目前的jvm類型,java可以通過兩種參數的方式來指定具體的jvm類型,一種按照jvm.cfg檔案中的jvm名稱指定,第二種方法是直接指定,它們執行的方法分别是“java -J”、“java -XXaltjvm=”或“java -J-XXaltjvm=”。如果是第一種參數傳遞方式,CheckJvmType函數會取參數‘-J’後面的jvm名稱,然後從已知的jvm配置參數中查找如果找到同名的則去掉該jvm名稱前的‘-’直接傳回該值;而第二種方法,會直接傳回“-XXaltjvm=”或“-J-XXaltjvm=”後面的jvm類型名稱;如果在運作java時未指定上面兩種方法中的任一一種參數,CheckJvmType會取配置檔案中第一個配置中的jvm名稱,去掉名稱前面的‘-’傳回該值。CheckJvmType函數的這個傳回值會在下面的函數中彙同jre路徑組合成jvm.dll的絕對路徑。如果沒有指定這會使用jvm.cfg中第一個定義的jvm.可以通過set _JAVA_LAUNCHER_DEBUG=1在控制台上測試.

最後獲得jvm.dll的路徑,JRE路徑+/bin+/jvm類型字元串+/jvm.dll就是jvm的檔案路徑了,但是如果在調用java程式時用-XXaltjvm=參數指定的路徑path,就直接用path+/jvm.dll檔案做為jvm.dll的檔案路徑.

  二:裝載jvm.dll

通過第一步已經找到了jvm的路徑,java通過LoadJavaVM來裝入jvm.dll檔案.裝入工作很簡單就是調用windows API函數:

LoadLibrary裝載jvm.dll動态連接配接庫.然後把jvm.dll中的導出函數JNI_CreateJavaVM和JNI_GetDefaultJavaVMInitArgs挂接到InvocationFunctions變量的CreateJavaVM和GetDefaultJavaVMInitArgs函數指針變量上。jvm.dll的裝載工作宣告完成。

  三:初始化jvm,獲得本地調用接口,這樣就可以在java中調用jvm的函數了.調用InvocationFunctions->CreateJavaVM也就是jvm中JNI_CreateJavaVM方法獲得JNIEnv結構的執行個體.

  四:運作java程式.

java程式有兩種方式一種是jar包,一種是class. 運作jar,java -jar XXX.jar運作的時候,java.exe調用GetMainClassName函數,該函數先獲得JNIEnv執行個體然後調用java類java.util.jar.JarFileJNIEnv中方法getManifest()并從傳回的Manifest對象中取getAttributes("Main-Class")的值即jar包中檔案:META-INF/MANIFEST.MF指定的Main-Class的主類名作為運作的主類。之後main函數會調用java.c中LoadClass方法裝載該主類(使用JNIEnv執行個體的FindClass)。main函數直接調用java.c中LoadClass方法裝載該類。如果是執行class方法。main函數直接調用java.c中LoadClass方法裝載該類。

然後main函數調用JNIEnv執行個體的GetStaticMethodID方法查找裝載的class主類中

“public static void main(String[] args)”方法,并判斷該方法是否為public方法,然後調用JNIEnv執行個體的

CallStaticVoidMethod方法調用該java類的main方法。

<script type="text/javascript"> </script> <script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"> </script>