1. 程式人生 > >JVM系列(一)—— 何為JVM

JVM系列(一)—— 何為JVM

JVM能夠跨計算機繫結構來執行JAVA位元組碼,主要是由於JVM遮蔽了與各個計算機平臺相關的軟體或硬體之間的差異,使得與平臺相關的耦合統一由JVM提供者來實現。

JVM的全稱是Java Virtual Machine(Java虛擬機器),它通過模擬一個計算機來達到一個計算機所具有的計算功能。我們先來看看一個真實的計算機如何才能具備計算的功能。

  • 指令集,這個計算機所能識別的機器語言的命令集合。
  • 計算單位,即能夠識別並且控制指令執行的功能模組。
  • 定址方式,地址的位數,最小地址和最大地址的範圍,以及地址的執行規則
  • 暫存器定義,包括運算元暫存器,變址暫存器,控制暫存器等的定義,數量和使用方式。
  • 儲存單元,能夠儲存運算元和儲存操作結構的單元,如核心快取,記憶體和磁碟等。
    上面幾個和我們所說的程式碼執行最密切的還是指令集部分,下面簡單說下計算機中指令集是如何定義的。

什麼是指令集,有何作用

所謂指令集就是在CPU中用來計算和控制計算機體系的一套指令的集合,每一種新型的CPU在設計時都規定了一些列與其他硬體電路配合的指令系統。而指令集的現金與否也關係到CPU的效能發揮,它是CPU效能的一個重要標誌。

當前計算機中有哪些指令集?
從主流的體系,分為精簡指令集RISC和複雜指令集CISC。

指令集與組合語言有什麼關係?
指令集是可以直接被機器識別的機器碼,也就是它必須以二進位制格式存在於計算機中。
而組合語言是能夠被人識別的指令,組合語言在順序和邏輯上是與機器指令一一對應的。換句話說,組合語言是為了讓人更容易地記住機器指令而使用的助記符。

指令集與CPU架構有何聯絡?
CPU的架構會影響到指令集。

回到JVM的主題中來,JVM和實體機到底有何不同呢?

  • 一個抽象規範,這個規範就約束了JVM到底是什麼,它有哪些組成部分,這些抽象的規範都在the java virtual machine specification中詳細描述了
  • 一個具體的實現,所謂具體的實現就是不同的廠商按照這個抽象的規範用軟體或軟體和硬體結合的方式在相同或者不同的平臺上的具體的實現。
  • 一個執行中的例項,當用其執行要給java程式時,他就是執行中的一個例項,麼個執行中的java程式都是一個jvm例項。
    JVM和時提及一樣也必須有一套合適的指令集,則個指令集能夠被JVM解析執行。這個指令集我們稱之為JVM位元組碼指令集,符合CLASS檔案規範的位元組碼都可以被JVM執行。

JVM體系結構

除了指令集,JVM還需要一下幾個部分

  • 類載入器,在JVM啟動時或者在類執行時將需要的class載入到JVM中。
  • 執行引擎,執行引擎的任務是負責執行class檔案中包含的位元組碼指令,相當於實際機器上的CPU。
  • 記憶體區,將記憶體區劃分成若干個區以模擬實際機器上的儲存,記錄和排程功能模組,如實際機器上的各種功能的暫存器或者PC指標的記錄器等。
  • 本地方法呼叫,呼叫c或者c++實現的本地方法的程式碼返回結果。
    這裡寫圖片描述

類載入器

每個被JVM裝在的型別都有一個對應的java.lang.Class類的例項來表示該型別,該例項可以唯一表示被JVM裝載的class類,要求這個例項和其他類的例項一樣都存放在java的堆中。

執行引擎

執行引擎是JVM的核心部分,執行引擎的作用就是解析JVM位元組碼指令,得到執行結果。在《Java虛擬機器規範》中詳細地定義了執行引擎遇到每條位元組碼指令時應該處理什麼,並且應該得到什麼結果。但是沒有規定執行引擎應該如何或採取什麼方式處理而叨叨這個結果。因為執行引擎具體採用什麼方式由JVM的實現廠家自己去實現,是直接解釋執行還是採用JIT技術轉成原生代碼區執行,還是採用暫存器這個晶片模式區執行都可以。所以,執行引擎的具體實現有很大的發揮空間。如sun的hotspot是基於棧的執行引擎,而google的dalvik是基於暫存器的執行引擎。
執行引擎也就是執行一條條程式碼的流程,而程式碼都是包含在方法體內的,所以執行引擎的本質上就是執行一個個方法所串起來的流程,對應到作業系統中一個執行流程就是一個Java程序還是一個java執行緒呢?很顯然是後者,因為一個java程序可以有多個執行的流程。這樣說來,每個java執行緒就是一個執行引擎的例項,那麼在一個JVM例項中就會同事有多個執行引擎在工作,這些執行引擎有的在執行使用者的程式,有的在執行內部的程式(如java垃圾收集器)

java記憶體管理

執行引擎在執行一段程式時需要儲存一些東西,如操作碼需要的運算元,操作碼的執行結果需要儲存。class類的位元組碼還有類的物件等資訊都需要在執行引擎執行之前就準備好。
從上圖看出一個JVM例項會有一個方法區,java堆,java棧,PC暫存器,和本地方法區。其中方法區和java堆是所有執行緒共享的,也就是可以被所有的執行引擎例項訪問。每個新的執行引擎例項被建立時會為這個執行引擎建立一個java棧和一個PC暫存器,如果當前正在執行與一個java方法,那麼當前的這個java棧中儲存的時該執行緒中方法呼叫的狀態,包括方法的引數,方法的區域性變數,方法的返回值以及運算的中間結果。而PC暫存器會指向即將執行的下一條指令。
如果是本地方法呼叫,則儲存在本地方法呼叫棧中或者特定實現中的某個記憶體區域中。

JVM工作機制

JVM是如何執行位元組碼命令的,即,前面所說的執行引擎是如何工作的。

機器如何執行程式碼

先看看實體機:
只接受機器指令,其他高階語言首先必須經過編譯器編譯成機器指令才能被計算機正確執行。
編譯器:與硬體耦合的部分就交給了編譯器,不同的硬體平臺通常需要的編譯器也不同。
當前,不同的硬體平臺的差異已經被更上一層的軟體平臺所代替了,這個軟體平臺就是作業系統,與其說不同的硬體平臺還不如說作業系統之間的差異,因為現在的作業系統幾乎完全遮蔽了硬體。所以,現在編譯器和作業系統的關係會更佳容易讓讓人理解。如C語言在windows下的編譯器為Microsoft C,而在linux下通常是gcc。
一個程式從編寫到執行的階段:
原始碼——》前處理器——》編譯器——》彙編程式——》目的碼——》連結器——》可執行程式
除了原始碼和最後的可執行程式,中間的所有環節都是由現代意義上的編譯器統一處理的。如,在Linux環境下,
我們通常安裝一個軟體需要經過configure、make、make install,make clean。
configure為這個程式在當前的作業系統下選擇合適的編譯器來編譯這個程式程式碼,也就是為這個程式程式碼選擇合適的編譯器和一些環境引數。
make自然就是對程式程式碼進行編譯操作了。它會將原始碼編譯成可執行的目標檔案。
make install將已經編譯好的可執行檔案安裝到作業系統指定或預設的安裝目錄下。
make clean用於刪除編譯時產生臨時的目錄或檔案

值得注意的是,我們通常所說的編譯器都是將某種高階語言直接編譯成可執行的目標機器語言(實際上,在windows下,是需要動態連結的目標二進位制檔案,DLL)但是實際上,還有有一些編譯器,是將一種高階語言編譯成另一種高階語言,或者將低階語言編譯成高階語言(反編譯),或者將高階語言編譯成虛擬機器目標語言,如JAVA比那一起。
再說,如何讓機器(不管是實體機還是虛擬機器)執行程式碼的主題,不管是何種指令集,都只有最基本的元素,加減乘除,求餘,求模等。這些運算又可以進一步分解成二進位制位運算,與或非,異或等。這些運算又可以通過指令完成,而指令的核心目的就是需要運算的種類(操作嗎)和運算所需要的資料(運算元),以及從哪裡(暫存器或棧)獲取運算元,將運算結果存放到什麼地方(暫存器或棧)等。這種不同的操作方式又將指令劃分為一地址指令,二地址指令,三地址指令,零地址指令等n地址指令。相應的指令集會有相應的架構實現,如基於暫存器的架構實現或基於棧的架構實現,這裡的基於暫存器或者棧都是指再一個指令中的運算元是如何存取的。

JVM為何選擇基於棧的架構

JVM執行位元組碼指令是基於棧的架構,也就是所有的運算元必須先入棧。
然後根據指令中的操作碼選擇從棧頂彈出若干個元素進行計算後再將結果壓入棧中。

原因:

  • JVM要設計成與平臺無關的,而平臺無關性就是要保證在沒有或者很少的暫存器的機器上也要同樣能正確地執行java程式碼。
  • 為了指令的緊湊性,因為java的位元組碼會在網路上傳輸,所以class檔案的大小也是設計JVM位元組碼指令的一個重要因素,如在class檔案中位元組碼除了處理兩個表跳轉的指令外,其他全都是位元組對齊的,操作嗎可以只佔一個位元組大小,這都是為了讓編譯後的class檔案更佳緊湊,為了提高位元組碼在網路上的傳輸效率。sun設計了一個jar報的壓縮工具pack200,它可以將多個class檔案中的重複的常量池的資訊進行合併,如一般在每個class檔案中都含有“.java/lang/String”,那麼多個class檔案中的常量就可以公用,從而減少資料量的作用。