1. 程式人生 > 其它 >JVM深入理解(三)-載入與位元組碼執行

JVM深入理解(三)-載入與位元組碼執行

載入與位元組碼執行

  1.   載入過程
  2.   載入器
  3.   位元組碼執行

1、載入過程

  類的整個生命過程:載入、連線(驗證、準備、解析)、初始化、使用和解除安裝五個階段

  

  • 載入階段。虛擬機器主要完成以下工作
    1. 通過一個類的全限定名來獲取定義此類的二進位制位元組流
    2. 將這個位元組流所代表的的靜態儲存結構轉換為方法區的執行時資料結構
    3. 在記憶體中生成一個代表這個類的java.lang.Class物件,作為方法區這些資料的訪問入口(對於HotSpot虛擬機器而言,Class 物件比較特殊,雖是物件,但是存放在方法區)
  • 驗證階段。包括 檔案格式驗證、元資料驗證、位元組碼驗證和符號引用驗證。
  • 準備階段。是正式為類變數分配記憶體
    並設定類變數初始值的階段。
    首先是這時候進行記憶體分配的僅包括類變數(被static修飾的變數),而不包括例項變數,例項變數將會在物件例項化時隨著物件一起分配在Java堆中。其次,這裡所說的初始值“通常情況下”是資料型別的零值
  public static int value = 123; // 在準備階段初始化為 0 
  public static final int value = 123; // 在準備階段初始化為 123
  • 解析階段。指檢查指定的類是否引用了其他的類、介面,包括類或介面、欄位、類方法、介面方法的解析。 是虛擬機器將常量池中的符號引用替換為直接引用的過程。

  

2、載入器

  

  只有兩個類由同一個類載入器載入才有意義。否則,即使兩個類來源於同一個Class檔案,只要載入他們的類載入器不同,這兩個類還是不同的。這裡所說的“相等”,包括代表類的Class物件的equal()方法、isAssignableFrom()方法、isInstance()方法的返回結果,同時也包括了使用instanceos關鍵字作物件所屬關係判定等情況。

  在java虛擬機器的角度來講,只存在兩種不同的類載入器:一種是啟動類載入器(Bootstrap ClassLoader),這個類載入器使用C++語言實現,是虛擬機器自身的一部分;另外一種就是所有其他的類載入器,這些類載入器都由java實現,獨立於虛擬機器外部,並且全部繼承在抽象類java.lang.ClassLoader。

  而從java開發人員的角度來講,類載入器還可以分為三類:

  • 啟動類載入器。這個類載入器負責將 <JAVA_HOME>\lib 目錄下的,或 -Xbootclasspath 引數所指定的路徑中的,並且是虛擬機器識別的類庫載入到虛擬機器中。啟動類載入器無法被java程式直接引用。
  • 擴充套件類載入器。這個載入器由 sun.misc.Launcher$ExtClassLoader實現,它負責載入<JAVA_HOME>\lib\ext目錄中,或者被java.ext.dirs系統變數所指定路徑中的所有類庫,開發者可以直接使用擴充套件類載入器。
  • 應用程式類載入器:這個載入器由sun.misc.Launcher$AppClassLoader來實現。由於這個類載入器是ClassLoader 中的 getSystemClassLoader() 方法的返回值,所以一般也稱他為系統類載入器。它負責載入使用者路徑上所指定的類庫,開覆轍可以直接使用這個類載入器,如果應用程式中沒有定義過自己的類載入器,一般情況下就是這個預設的類載入器。

  

  這就是雙親委派模型:如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把這個請求委派給父類載入器去完成,每一個層次的類載入器都是如此,因此所有的載入請求最終都應該傳送到頂層的類載入器中,只有當父載入器反饋自己無法完成這個載入請求時,子載入器才嘗試自己去載入。 3、位元組碼執行   棧幀結構
棧幀是用於支援虛擬機器進行方法呼叫和方法執行的資料結構,它是虛擬機器執行時資料區中的虛擬機器棧的棧元素。棧幀儲存了方法的區域性變量表、運算元棧、動態連線和方法返回地址等資訊。      
  • 當一個方法開始執行的時候,這個方法的運算元棧是空的,在方法的執行過程中,會有各種位元組碼指令向運算元棧中寫入和提取內容,也就是入棧和出棧的操作。每個棧幀都包含一個指向執行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支援方法呼叫過程中的動態連結。

    在 Class 檔案裡面,描述一個方法呼叫了其他方法,或者訪問其成員變數是通過符號引用(Symbolic Reference)來表示的,動態連結的作用就是將這些符號引用所表示的方法轉換為實際方法的直接引用。類載入的過程中將要解析掉尚未被解析的符號引用,並且將變數訪問轉化為訪問這些變數的儲存結構所在的執行時記憶體位置的正確偏移量。

    由於動態連結的存在,通過晚期繫結(Late Binding)使用的其他類的方法和變數在發生變化時,將不會對呼叫它們的方法構成影響。

  • 方法呼叫
    方法呼叫不等同與方法執行,方法呼叫階段唯一的任務就是確定被呼叫方法的版本(呼叫哪一個方法)

    我們會需要解析,分派,還要考慮到動態型別語言的特點。解析呼叫一定是個靜態的過程,在編譯期間就完全確定,在類裝載的解析階段就會把涉及的符號引用全部轉變為可確定的直接引用,不會延遲到執行期再去完成。而分派呼叫則可能是靜態的也可能是動態的,根據分派依據的宗量數可分為單分派和多分派。
    動態型別語言的關鍵特徵是它的型別檢查的主體過程是在執行期而不是編譯期,如JavaScript、Python、Ruby、Lisp等

    動態分派我們以 invokevirtual 為例

    1. 找到運算元棧頂的第一個元素所指向的物件的實際型別,記作C
    2. 如果找到與常量中的描述符和簡單名稱都相符的方法,則進行訪問許可權校驗,如果通過則返回這個方法的直接引用,查詢結束;不通過則返回java.lang.IllegalAccessError異常
    3. 否則,按照繼承關係從下往上依次對C的各個父類進行第二步的搜尋驗證
    4. 如果始終找不到合適的方法,則丟擲java.lang.AbstractMethodError異常

最後就是 基於棧的位元組碼解釋執行


轉載自連結:https://www.jianshu.com/p/2c059bd380e4