1. 程式人生 > >JVM (二)--Java程式編譯、類載入及執行

JVM (二)--Java程式編譯、類載入及執行

Java程式的編譯

Java程式的編譯是由Java原始碼編譯器來完成,流程圖如下:

Java程式的執行

Java程式的編譯是由Java執行引擎來完成,流程圖如下:

 

Java程式碼編譯和執行的整個過程包含了以下三個重要的機制:

  • Java原始碼編譯機制

  • 類載入機制

  • 類執行機制

一、Java原始碼編譯機制

Java 原始碼編譯由以下三個過程組成:

  • 分析和輸入到符號表
  • 註解處理
  • 語義分析和生成class檔案

流程圖如下所示:

最後生成的class檔案由以下部分組成:

  • 結構資訊。包括class檔案格式版本號及各部分的數量與大小的資訊
  • 元資料。對應於Java原始碼中宣告與常量的資訊。包含類/繼承的超類/實現的介面的宣告資訊、域與方法宣告資訊和常量池
  • 方法資訊。對應Java原始碼中語句和表示式對應的資訊。包含位元組碼、異常處理器表、求值棧與區域性變數區大小、求值棧的型別記錄、除錯符號資訊

二、類載入機制

JVM將類載入過程分為三個步驟:裝載(Load),連結(Link)和初始化(Initialize)連結又分為三個步驟,如下圖所示:

1) 裝載:查詢並載入類的二進位制資料;

2)連結:

驗證:確保被載入類的正確性;

準備:為類的靜態變數分配記憶體,並將其初始化為預設值;

解析:把類中的符號引用轉換為直接引用;

3)初始化:為類的靜態變數賦予正確的初始值;

          那為什麼我要有驗證這一步驟呢?首先如果由編譯器生成的class檔案,它肯定是符合JVM位元組碼格式的,但是萬一有高手自己寫一個class檔案,讓JVM載入並執行,用於惡意用途,就不妙了,因此這個class檔案要先過驗證這一關,不符合的話不會讓它繼續執行的,也是為了安全考慮吧。

        準備階段和初始化階段看似有點牟盾,其實是不牟盾的,如果類中有語句:private static int a = 10,它的執行過程是這樣的,首先位元組碼檔案被載入到記憶體後,先進行連結的驗證這一步驟,驗證通過後準備階段,給a分配記憶體,因為變數a是static的,所以此時a等於int型別的預設初始值0,即a=0,然後到解析(後面在說),到初始化這一步驟時,才把a的真正的值10賦給a,此時a=10。

1、類的初始化

 只有下面6中情況才會導致類的類的初始化:

1)建立類的例項,也就是new一個物件

2)訪問某個類或介面的靜態變數,或者對該靜態變數賦值

3)呼叫類的靜態方法

4)反射(Class.forName("com.lyj.load"))

5)初始化一個類的子類(會首先初始化子類的父類)

6)JVM啟動時標明的啟動類,即檔名和類名相同的那個類

 

類的初始化步驟:

1)如果這個類還沒有被載入和連結,那先進行載入和連結

 2)假如這個類存在直接父類,並且這個類還沒有被初始化(注意:在一個類載入器中,類只能初始化一次),

       那就初始化直接的父類(不適用於介面)

3)加入類中存在初始化語句(如static變數和static塊),那就依次執行這些初始化語句。

2.類的載入

       類的載入指的是將類的.class檔案中的二進位制資料讀入到記憶體中,將其放在執行時資料區的方法區內,然後在堆區建立一個這個類的Java.lang.Class物件,用來封裝類在方法區類的物件。

類的載入的最終產品是位於堆區中的Class物件
Class物件封裝了類在方法區內的資料結構,並且向Java程式設計師提供了訪問方法區內的資料結構的介面

載入類的方式有以下幾種:

1)從本地系統直接載入

2)通過網路下載.class檔案

3)從zip,jar等歸檔檔案中載入.class檔案

4)從專有資料庫中提取.class檔案

5)將Java原始檔動態編譯為.class檔案(伺服器)

3.類載入器

類載入器的雙親委派模型:雙親委派模型是一種組織類載入器之間關係的一種規範,他的工作原理是:如果一個類載入器收到了類載入的請求,它不會自己去嘗試載入這個類,而是把這個請求委派給父類載入器去完成,這樣層層遞進,最終所有的載入請求都被傳到最頂層的啟動類載入器中,只有當父類載入器無法完成這個載入請求(它的搜尋範圍內沒有找到所需的類)時,才會交給子類載入器去嘗試載入.

JVM的類載入是通過ClassLoader及其子類來完成的,類的層次關係和載入順序可以由下圖來描述:

啟動類載入器(Bootstrap ClassLoader):這個類載入器負責將<JAVA_HOME>\lib目錄下的類庫載入到虛擬機器記憶體中,用來載入java的核心庫,此類載入器並不繼承於java.lang.ClassLoader,不能被java程式直接呼叫,程式碼是使用C++編寫的.是虛擬機器自身的一部分.

擴充套件類載入器(Extendsion ClassLoader):這個類載入器負責載入<JAVA_HOME>\lib\ext目錄下的類庫,用來載入java的擴充套件庫,開發者可以直接使用這個類載入器.

應用程式類載入器(Application ClassLoader):這個類載入器負責載入使用者類路徑(CLASSPATH)下的類庫,一般我們編寫的java類都是由這個類載入器載入,這個類載入器是CLassLoader中的getSystemClassLoader()方法的返回值,所以也稱為系統類載入器.一般情況下這就是系統預設的類載入器.

除此之外,我們還可以加入自己定義的類載入器,以滿足特殊的需求,需要繼承java.lang.ClassLoader類.

載入過程中會先檢查類是否被已載入,檢查順序是自底向上,從Application ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已載入就視為已載入此類,保證此類只所有ClassLoader載入一次。而載入的順序是自頂向下,也就是由上層來逐層嘗試載入此類。

三、類執行機制

JVM是基於棧的體系結構來執行class位元組碼的。執行緒建立後,都會產生程式計數器(PC)和棧(Stack),程式計數器存放下一條要執行的指令在方法內的偏移量,棧中存放一個個棧幀,每個棧幀對應著每個方法的每次呼叫,而棧幀又是有區域性變數區和運算元棧兩部分組成,區域性變數區用於存放方法中的區域性變數和引數,運算元棧中用於存放方法執行過程中產生的中間結果

棧的結構如下圖所示: