Java查漏系列(1)——JVM

一年多的時間沒搞過java了,最近在學習hadoop,所以想把java再撿一撿,以前主要關注java的語法和api等,很少關注底層內存等,所以現在找時間把這塊撿撿。

JVM,全稱java virtual machine,也就是java虛擬機,望文生義的解釋就是在計算機上面再虛擬出一個執行java程序的計算機。具體一點說,jvm可以被看成一個想象中的機器,在實際的計算機上通過軟件模擬來實現,有自己想象中的硬件,如處理器、堆棧、寄存器等,還有自己相應的指令系統。JVM在它的生存週期中有一個明確的任務,那就是運行Java程序,因此當Java程序啓動的時候,就產生JVM的一個實例;當程序運行結束的時候,該實例也跟着消失了。下面分別從JVM在系統中所處的位置、JVM的體系結構和JVM的運行過程三個方面來對JVM做一下介紹。

1.JVM在系統中的位置

首先,從宏觀上看,JVM就是運行於具體操作系統上面的一個軟件,如下圖所示:


在整個java平臺中,JVM是介於java應用程序與具體操作系統之間的一個組件,JVM在java平臺中的位置如下圖所示:


在Java平臺的結構中, 可以看出,Java虛擬機(JVM) 處在覈心的位置,是程序與底層操作系統和硬件無關的關鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統, 其中依賴於平臺的部分稱爲適配器;JVM 通過移植接口在具體的平臺和操作系統上實現;在JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺, 就是因爲有Java虛擬機(JVM)實現了程序與操作系統的分離,從而實現了Java 的平臺無關性。

2.JVM體系結構

每個JVM都有兩種機制,一個是裝載具有合適名稱的類(類或是接口),叫做Class Loader;另外的一個負責執行包含在已裝載的類或接口中的指令,叫做Execution Engine。每個JVM又包括Method Area、Heap、 Stack、PC Register和Native Method Stack這五個部分,這幾個部分和類裝載機制與運行引擎機制一起組成的體系結構圖爲:


該圖參考了網上廣爲流傳的JVM構成圖,從這個圖中可以看出,整個JVM分爲以下四個部分:

1)Class Loader(類裝載器)

類加載器的作用是加載類文件到內存,比如編寫一個HelloWord.java程序,然後通過javac編譯成class文件,那怎麼才能加載到內存中被執行呢?Class Loader承擔的就是這個責任,並不是隨便建立一個.class文件就能被加載的,Class Loader加載的class文件是有格式要求的,另外,Class Loader只管加載,只要符合文件結構就加載,至於說能不能運行,則不是它負責的,那是由ExecutionEngine負責的。

2)Execution Engine(執行引擎)

執行引擎也叫做解釋器(Interpreter),負責解釋命令,提交操作系統執行。執行引擎處於JVM的核心位置,在Java虛擬機規範中,它的行爲是由指令集所決定的。Java指令集相當於Java程序的彙編語言。Java指令集中的指令包含一個單字節的操作符,用於指定要執行的操作,還有0個或多個操作數,提供操作所需的參數或數據。許多指令沒有操作數,僅由一個單字節的操作符構成。

虛擬機的內層循環的執行過程如下:

do{
	取一個操作符字節;
	根據操作符的值執行一個動作;
}while(程序未結束);

由於指令系統的簡單性,使得虛擬機執行的過程十分簡單,從而有利於提高執行的效率。

3)Native Interface(本地接口)

本地接口的作用是融合不同的編程語言爲Java所用,它的初衷是融合C/C++程序,Java誕生的時候是C/C++橫行的時候,要想立足,必須有一個聰明的、睿智的調用C/C++程序,於是就在內存中專門開闢了一塊區域處理標記爲native的代碼,它的具體做法是Native MethodStack中登記native方法,在ExecutionEngine執行時加載nativelibraies。目前該方法使用的是越來越少了,除非是與硬件有關的應用,比如通過Java程序驅動打印機,或者Java系統管理生產設備,在企業級應用中已經比較少見,因爲現在的異構領域間的通信很發達,比如可以使用Socket通信,也可以使用Web Service等等,不多做介紹。

4)Runtime Data Area(運行時數據區)

運行時數據區是整個JVM的重點,我們所有寫的程序都被加載到這裏,之後纔開始運行,Java生態系統如此的繁榮,得益於該區域的優良自治。其中,Method Area和Heap是基於JVM實例的,即JVM的每個實例都有一個它自己的方法域和一個堆,運行於JVM內的所有的線程都共享這些區域,當虛擬機裝載類文件的時候,它解析其中的二進制數據所包含的類信息,並把它們放到方法域中;當程序運行的時候,JVM把程序初始化的所有對象置於堆上。PC Register和Stack是基於線程的,即每個線程創建的時候,都會擁有自己的程序計數器和 Java棧,其中程序計數器中的值指向下一條即將被執行的指令,線程的Java棧則存儲爲該線程調用Java方法的狀態。本地方法調用的狀態被存儲在NativeMethod  Stack,該方法棧依賴於具體的實現。

由於運行時數據區是整個JVM的重點,所以下一節詳細介紹。

3.JVM運行過程

java源文件經過java編譯器編譯生成字節碼文件(.class),字節碼文件要能夠在JVM中執行,需要經過以下三個步驟:裝載、鏈接、初始化。下面分別對這三個步驟進行說明:

裝載:類的裝載是通過類裝載器來完成的,類裝載器需要完成的功能是定義一個java類,即把java字節碼轉換成JVM中的java.lang.class類的對象。所有的java類在JVM中的表現形式都是java.lang.class類的對象。

鏈接:java類的鏈接指的是將java類的二進制代碼合併到JVM的運行狀態之中的過程。在鏈接之前,這個類必須被成功加載。類的鏈接包括驗證、準備和解析等幾個步驟。驗證用來確保java類的二進制表示在結構上是完全正確的,如果驗證過程出現錯誤的話,會拋出java.lang.VerifyError錯誤。準備過程是創建java類中的靜態域,並將這些域設爲默認值,準備過程並不會執行代碼。在一個java類中會包含對其它類或接口的形式引用,包括它的父類、所實現的接口、方法的形式參數和返回值的java類等,解析的過程就是確保這些被引用的類能被正確的找到,解析的過程可能會導致其它的java類被加載。

初始化:當一個java類第一次被真正使用到的時候,JVM會進行該類的初始化操作,初始化過程的主要操作是執行靜態代碼塊和初始化靜態域。在一個類被初始化之前,它的直接父類也需要被初始化。

下面通過一個例子來說明JVM的運行過程:

class HelloApp 
{
	public static int x = 10;
	public static void main(String[] args) 
	{
		System.out.println(x);
	}
	static{
		x = 30;
	}
}

其執行流程如下:

開始試圖執行類HelloApp的main方法,發現該類並沒有被裝載,也就是說虛擬機當前不包含該類的二進制代表,於是虛擬機使用 ClassLoader試圖尋找這樣的二進制代表。如果這個進程失敗,則拋出一個異常。類被裝載後同時在main方法被調用之前,必須對類 HelloApp與其它類型進行鏈接然後初始化。以上程序中,靜態域爲x,靜態代碼塊部分也是給x賦值,初始化完成後,x的值爲30。

下一節重點介紹一下JVM中的Runtime Data Area這一區域。


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章