Java虛擬機器(JVM)是可運行Java代碼的假想電腦。只要根據JVM規格描述將解譯器移植到特定的電腦上,就能保證經過編譯的任何Java代碼能夠在該系統上運行。本文首先簡要介紹從Java檔案的編譯到最終執行的過程,隨後對JVM規格描述作一說明。
一.Java源檔案的編譯、下載、解釋和執行
Java應用程式的開發週期包括編譯、下載、解釋和執行幾個部分。Java編譯器將Java來源程式翻譯為JVM可執行代碼?位元組碼。這一編譯過程同C/C++的編譯有些不同。當C編譯器編譯產生一個對象的代碼時,該代碼是為在某一特定硬體平台運行而產生的。因此,在編譯過程中,編譯器通過查表將所有對符號的引用轉換為特定的記憶體位移量,以保證程式運行。Java編譯器卻不將對變數和方法的引用編譯為數值引用,也不確定程式執行過程中的記憶體布局,而是將這些符號引用資訊保留在位元組碼中,由解譯器在運行過程中創立記憶體布局,然後再通過查表來確定一個方法所在的地址。這樣就有效保證了Java的可移植性和安全性。
運行JVM位元組碼的工作是由解譯器來完成的。解釋執行過程分三部進行:代碼的裝入、代碼的校正和代碼的執行。裝入代碼的工作由"類裝載器"(ClassLoader)完成。類裝載器負責裝入運行一個程式需要的所有代碼,這也包括程式碼中的類所繼承的類和被其調用的類。當類裝載器裝入一個類時,該類被放在自己的名字空間中。除了通過符號引用自己名字空間以外的類,類之間沒有其他辦法可以影響其他類。在本台電腦上的所有類都在同一地址空間內,而所有從外部引進的類,都有一個自己獨立的名字空間。這使得本地類通過共用相同的名字空間獲得較高的運行效率,同時又保證它們與從外部引進的類不會相互影響。當裝入了運行程式需要的所有類後,解譯器便可確定整個可執行程式的記憶體布局。解譯器為符號引用同特定的地址空間建立對應關係及查詢表。通過在這一階段確定代碼的記憶體布局,Java很好地解決了由超類改變而使子類崩潰的問題,同時也防止了代碼對地址的非法訪問。
隨後,被裝入的代碼由位元組碼校正器進行檢查。校正器可發現運算元棧溢出,非法資料類型轉化等多種錯誤。通過校正後,代碼便開始執行了。Java位元組碼的執行有兩種方式:
1.即時編譯方式:解譯器先將位元組碼編譯成機器碼,然後再執行該機器碼。
2.解釋執行方式:解譯器通過每次解釋並執行一小段代碼來完成Java位元組碼程 序的所有操作。
通常採用的是第二種方法。由於JVM規格描述具有足夠的靈活性,這使得將位元組碼翻譯為機器代碼的工作具有較高的效率。對於那些對運行速度要求較高的應用程式,解譯器可將Java位元組碼即時編譯為機器碼,從而很好地保證了Java代碼的可移植性和高效能。
二.JVM規格描述
JVM的設計目標是提供一個基於抽象規格描述的電腦模型,為解釋程式開發人員提很好的靈活性,同時也確保Java代碼可在符合該規範的任何系統上運行。JVM對其實現的某些方面給出了具體的定義,特別是對Java可執行代碼,即位元組碼(Bytecode)的格式給出了明確的規格。這一規格包括作業碼和運算元的文法和數值、標識符的數值表示方式、以及Java類檔案中的Java對象、常量緩衝池在JVM的儲存映象。這些定義為JVM解譯器開發人員提供了所需的資訊和開發環境。Java的設計者希望給開發人員以隨心所欲使用Java的自由。
JVM定義了控制Java代碼解釋執行和具體實現的五種規格,它們是:
JVM指令系統
JVM寄存器
JVM棧結構
JVM片段回收堆
JVM儲存區
2.1 JVM指令系統
JVM指令系統同其他電腦的指令系統極其相似。Java指令也是由
作業碼和運算元兩部分組成。作業碼為8位位元,運算元進緊隨在作業碼的後面,其長度根據需要而不同。作業碼用於指定一條指令操作的性質(在這裡我們採用彙編符號的形式進行說明),如iload表示從儲存空間中裝入一個整數,anewarray表示為一個新數組分配空間,iand表示兩個整數的"與",ret用於流程式控制制,表示從對某一方法的調用中返回。當長度大於8位時,運算元被分為兩個以上位元組存放。JVM採用了"big endian"的編碼方式來處理這種情況,即高位bits存放在低位元組中。這同 Motorola及其他的RISC CPU採用的編碼方式是一致的,而與Intel採用的"little endian "的編碼方式即低位bits存放在低位位元組的方法不同。
Java指令系統是以Java語言的實現為目的設計的,其中包含了用於調用方法和監視多先程系統的指令。Java的8位作業碼的長度使得JVM最多有256種指令,目前已使用了160多種作業碼。
2.2 JVM指令系統
所有的CPU均包含用於儲存系統狀態和處理器所需資訊的寄存器組。如果虛擬機器定義較多的寄存器,便可以從中得到更多的資訊而不必對棧或記憶體進行訪問,這有利於提高運行速度。然而,如果虛擬機器中的寄存器比實際CPU的寄存器多,在實現虛擬機器時就會佔用處理器大量的時間來用常規儲存空間類比寄存器,這反而會降低虛擬機器的效率。針對這種情況,JVM只設定了4個最為常用的寄存器。它們是:
pc程式計數器
optop運算元棧頂指標
frame當前執行環境指標
vars指向當前執行環境中第一個局部變數的指標
所有寄存器均為32位。pc用於記錄程式的執行。optop,frame和vars用於記錄指向Java棧區的指標。
2.3JVM棧結構
作為基於棧結構的電腦,Java棧是JVM儲存資訊的主要方法。當JVM得到一個Java位元組碼應用程式後,便為該代碼中一個類的每一個方法建立一個棧架構,以儲存該方法的狀態資訊。每個棧架構套件括以下三類資訊:
局部變數
執行環境
運算元棧
局部變數用於儲存一個類的方法中所用到的局部變數。vars寄存器指向該變數表中的第一個局部變數。
執行環境用於儲存解譯器對Java位元組碼進行解釋過程中所需的資訊。它們是:上次調用的方法、局部變數指標和運算元棧的棧頂和棧底指標。執行環境是一個執行一個方法的控制中心。例如:如果解譯器要執行iadd(整數加法),首先要從frame寄存器中找到當前執行環境,而後便從執行環境中找到運算元棧,從棧頂彈出兩個整數進行加法運算,最後將結果壓入棧頂。
運算元棧用於儲存運算所需運算元及運算的結果。
2.4JVM片段回收堆
Java類的執行個體所需的儲存空間是在堆上分配的。解譯器具體承擔為類執行個體分配空間的工作。解譯器在為一個執行個體分配完儲存空間後,便開始記錄對該執行個體所佔用的記憶體地區的使用。一旦對象使用完畢,便將其回收到堆中。
在Java語言中,除了new語句外沒有其他方法為一對象申請和釋放記憶體。對記憶體進行釋放和回收的工作是由Java運行系統承擔的。這允許Java運行系統的設計者自己決定片段回收的方法。在SUN公司開發的Java解譯器和Hot Java環境中,片段回收用後台線程的方式來執行。這不但為運行系統提供了良好的效能,而且使程式設計人員擺脫了自己控制記憶體使用量的風險。
2.5JVM儲存區
JVM有兩類儲存區:常量緩衝池和方法區。常量緩衝池用於儲存類名稱、方法和欄位名稱以及串常量。方法區則用於儲存Java方法的位元組碼。對於這兩種儲存地區具體實現方式在JVM規格中沒有明確規定。這使得Java應用程式的儲存布局必須在運行過程中確定,依賴於具體平台的實現方式。
JVM是為Java位元組碼定義的一種獨立於具體平台的規格描述,是Java平台獨立性的基礎。目前的JVM還存在一些限制和不足,有待於進一步的完善,但無論如何,JVM的思想是成功的。
對比分析:如果把Java原程式想象成我們的C++原程式,Java原程式編譯後產生的位元組碼就相當於C++原程式編譯後的80x86的機器碼(二進位程式檔案),JVM虛擬機器相當於80x86電腦系統,Java解譯器相當於80x86CPU。在80x86CPU上啟動並執行是機器碼,在Java解譯器上啟動並執行是Java位元組碼。
Java解譯器相當於運行Java位元組碼的“CPU”,但該“CPU”不是通過硬體實現的,而是用軟體實現的。Java解譯器實際上就是特定的平台下的一
個應用程式。只要實現了特定平台下的解譯器程式,Java位元組碼就能通過解譯器程式在該平台下運行,這是Java跨平台的根本。當前,並不是在所有的平台下都有相應Java解譯器程式,這也是Java並不能在所有的平台下都能啟動並執行原因,它只能在已實現了Java解譯器程式的平台下運行。
推薦書籍:《深入理解電腦系統》《深入java虛擬機器》
引自:http://www.ej38.com/showinfo/java-167500.html