4、JVM執行子程序

一、class文件結構

1、魔數與 Class 文件的版本

每個 Class 文件的頭 4 個字節稱爲魔數(Magic Number),它的唯一作用是確定這個文件是否爲一個能被虛擬機接受的 Class 文件。使用魔數而不是擴展名來進行識別主要是基於安全方面的考慮,因爲文件擴展名可以隨意地改動。文件格式的制定者可以自由地選擇魔數值,只要這個魔數值還沒有被廣泛採用過同時又不會引起混淆即可
在這裏插入圖片描述

緊接着魔數的 4 個字節存儲的是Class 文件的版本號:第 5 和第 6 個字節是次版本號(MinorVersion),第 7 和第 8 個字節是主版本號(Major Version)。 Java 的版本號是從 45 開始的,JDK 1.1 之後的每個 JDK 大版本發佈主版本號向上加 1 高版本的 JDK 能向下兼容以前版本的 Class 文件,但不能運行以後版本的 Class 文件,即使文件格式並未發生任何變化,虛擬機也必須拒絕執行超過其版本號的 Class 文件。
在這裏插入圖片描述
我這裏使用的是你jdk1.8。

2、常量池

常量池中常量的數量是不固定的,所以在常量池的入口需要放置一項 u2 類型的數據,代表常量池容量計數值(constant_pool_count)。與 Java 中語言習 慣不一樣的是,這個容量計數是從 1 而不是 0 開始的
常量池中主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic References)。
字面量比較接近於 Java 語言層面的常量概念,如文本字符串、聲明爲 final 的常量值等。 而符號引用則屬於編譯原理方面的概念,包括了下面三類常量:類和接口的全限定名(Fully Qualified Name)、字段的名稱和描述符(Descriptor)、方法的名稱和描述符

3、訪問標誌

用於識別一些類或者接口層次的訪問信息,包括:這個 Class 是類還是接口;是否定義爲 public 類型;是否定義爲 abstract 類型;如果是類的話,是否被 聲明爲 final 等

4、類索引、父類索引與接口索引集合

這三項數據來確定這個類的繼承關係。類索引用於確定這個類的全限定名,父類索引用於確定這個類的父類的全限定名。

5、字段表集合

描述接口或者類中聲明的變量。字段(field)包括類級變量以及實例級變量。 而字段叫什麼名字、字段被定義爲什麼數據類型,這些都是無法固定的,只能引用常量池中的常量來描述。 字段表集合中不會列出從超類或者父接口中繼承而來的字段,但有可能列出原本 Java 代碼之中不存在的字段,譬如在內部類中爲了保持對外部類的訪問 性,會自動添加指向外部類實例的字段。

6、方法表集合

描述了方法的定義

7、屬性表集合

存儲 Class 文件、字段表、方法表都自己的屬性表集合,以用於描述某些場景專有的信息。如方法的代碼就存儲在 Code 屬性表中。

8、字節碼指令

Java 虛擬機的指令由一個字節長度的、代表着某種特定操作含義的數字(稱爲操作碼,Opcode)以及跟隨其後的零至多個代表此操作所需參數(稱爲操 作數,Operands)而構成。

9、動態鏈接

既然是執行方法,那麼我們需要知道當前棧幀執行的是哪個方法,棧幀中會持有一個引用(符號引用),該引用指向某個具體方法。

10、方法返回地址

方法退出方式有:正常退出與異常退出 理論上,執行完當前棧幀的方法,需要返回到當前方法被調用的位置,所以棧幀需要記錄一些信息,用來恢復上層方法的執行狀態。正常退出,上層方 法的 PC 計數器可以做爲當前方法的返回地址,被保存在當前棧幀中。“異常退出時,返回地址是要通過異常處理器表來確定的,棧幀中一般不會保存這部 分信息” 方法退出時會做的操作:恢復上次方法的局部變量表、操作數棧,把當前方法的返回值,壓入調用者棧幀的操作數棧中,使用當前棧幀保存的返回地址 調整 PC 計數器的值,當前棧幀出棧,隨後,執行 PC 計數器指向的指令。

11、附加信息

虛擬機規範允許實現虛擬機時增加一些額外信息,例如與調試相關的信息。 一般把動態連接、方法返回地址、其他額外信息歸成一類,稱爲棧幀信息。

二、基於棧的字節碼解釋執行引擎

Java 編譯器輸出的指令流,基本上是一種基於棧的指令集架構,指令流中的指令大部分都是零地址指令,它們依賴操作數棧進行工作。與 基於寄存器的指令集,最典型的就是 x86 的二地址指令集,說得通俗一些,就是現在我們主流 PC 機中直接支持的指令集架構,這些指令依賴寄存器進行 工作。

1、基於棧的指令集

舉個最簡單的例子,分別使用這兩種指令集計算“1+1”的結果,基於棧的指令集會是這樣子的: iconst_1
iconst_1
iadd
istore_0
兩條 iconst_1 指令連續把兩個常量 1 壓入棧後,iadd 指令把棧頂的兩個值出棧、相加,然後把結果放回棧頂,最後 istore_0 把棧頂的值放到局部變量表的 第0個Slot中。

2、基於寄存器的指令集

mov eax,1
add eax,1
mov 指令把 EAX 寄存器的值設爲 1,然後 add 指令再把這個值加 1,結果就保存在 EAX 寄存器裏面。

基於棧的指令集主要的優點就是可移植,寄存器由硬件直接提供,程序直接依賴這些硬件寄存器則不可避免地要受到硬件的約束。棧架構指令集的主要 缺點是執行速度相對來說會稍慢一些。所有主流物理機的指令集都是寄存器架構也從側面印證了這一點。

三、方法調用詳解

1、靜態解析

在編譯器就可以確定,因此確定 靜態分派的動作實際上不是由虛擬機來執行的。多見於:函數重載、模版等。

2、動態解析

需要在程序運行時才能決定到底調用那個函數模塊,多見於多態,繼承。
每個類在方法區會保存一張虛函數表,如果子類未重寫父類的方法,則子類中的虛函數指針指向父類中的函數,如果重寫了,則指向自己的函數。

在object中存在的動態鏈接會保存當前多想指向的虛函數表的地址,區尋找調用的函數。

四、jvm類加載機制

類從被加載到虛擬機內存中開始,到卸載出內存爲止,它的整個生命週期包括:加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、 初始化(Initialization)、使用(Using)和卸載(Unloading)7 個階段。其中驗證、準備、解析 3 個部分統稱爲連接(Linking)。
在這裏插入圖片描述

1、加載

虛擬機需要完成以下 3 件事情:

  1. 通過一個類的全限定名來獲取定義此類的二進制字節流。
  2. 將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構
  3. 在內存中生成一個代表這個類的 java.lang.Class 對象,作爲方法區這個類的各種數據的訪問入口。

2、驗證

是連接階段的第一步,這一階段的目的是爲了確保 Class 文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。但從整體 上看,驗證階段大致上會完成下面 4 個階段的檢驗動作:文件格式驗證、元數據驗證、字節碼驗證、符號引用驗證。

3、準備

是正式爲類變量分配內存並設置類變量初始值的階段,這些變量所使用的內存都將在方法區中進行分配。這個階段中有兩個容易產生混淆的概念需要強 調一下,首先,這時候進行內存分配的僅包括類變量(被 static 修飾的變量),而不包括實例變量,實例變量將會在對象實例化時隨着對象一起分配在 Java 堆中。其次,這裏所說的初始值“通常情況”下是數據類型的零值

4、解析

是虛擬機將常量池內的符號引用替換爲直接引用的過程。

5、初始化

在初始化階段,則根據程序員通過程 序制定的主觀計劃去初始化類變量和其他資源,或者可以從另外一個角度來表達:初始化階段是執行類構造器()方法的過程。()方 法是由編譯器自動收集類中的所有類變量的賦值動作和靜態語句塊(static{}塊)中的語句合併產生的,編譯器收集的順序是由語句在源文件中出現的順序 所決定的。

五、類加載器

對於任意一個類,都需要由加載它的類加載器和這個類本身一同確立其在 Java 虛擬機中的唯一性,每一個類加載器,都擁有一個獨立的類名稱空間。這 句話可以表達得更通俗一些:比較兩個類是否“相等”,只有在這兩個類是由同一個類加載器加載的前提下才有意義,否則,即使這兩個類來源於同一 個 Class 文件,被同一個虛擬機加載,只要加載它們的類加載器不同,那這兩個類就必定不相等。

這裏所指的“相等”,包括代表類的 Class 對象的 equals()方法、isAssignableFrom()方法、isInstance()方法的返回結果,也包括使用 instanceof 關 鍵字做對象所屬關係判定等情況

1、加解密案例(deencrpt 代碼)

我們可以繼承ClassLoader來實現自己的類加載器,實現特定的功能,例如加解密等服務。

加解密的項目中運用:可以使用把代碼使用私鑰加密,在解析階段使用公鑰解密。這樣跟用戶做項目時提供對應的公鑰,自己提供私鑰加密後的代碼信 息。在類加載時使用公鑰解密運行。這樣可以可以確保源代碼的保密性。

2、雙親委派模型

某個特定的類加載器在接到加載類的請求時,首先將加載任務委託給父類加載器,依次遞歸,如果父類加載器可以完成類加載任務,就成功返回;只有父類加載器無法完成此加載任務時,才自己去加載。

雙親委派模型好處:
Java類隨着它的類加載器一起具備了帶有優先級的層次關係,保證java程序穩定運行
在這裏插入圖片描述
啓動類加載器(Bootstrap ClassLoader):這個類將器負責將存放在<JAVA_HOME>\lib 目錄中的,或者被-Xbootclasspath 參數所指定的路徑中的,並且是 虛擬機識別的(僅按照文件名識別,如 rt.jar,名字不符合的類庫即使放在 lib 目錄中也不會被加載)類庫加載到虛擬機內存中。啓動類加載器無法被 Java 程序直接引用,用戶在編寫自定義類加載器時,如果需要把加載請求委派給引導類加載器,那直接使用 null 代替即可。

擴展類加載器(Extension ClassLoader):這個加載器由 sun.misc.Launcher$ExtClassLoader 實現,它負責加載<JAVA_HOME>\lib\ext 目錄中的,或者被 java.ext.dirs 系統變量所指定的路徑中的所有類庫,開發者可以直接使用擴展類加載器。

應用程序類加載器(Application ClassLoader):這個類加載器由 sun.misc.Launcher $App-ClassLoader 實現。由於這個類加載器是 ClassLoader 中的 getSystemClassLoader()方法的返回值,所以一般也稱它爲系統類加載器。它負責加載用戶類路徑(ClassPath)上所指定的類庫,開發者可以直接使用這 個類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。

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