jvm運行原理以及類加載器實例詳解

JVM運行原理

首先從“.java”代碼文件,編譯成“.class”字節碼文件,然後類加載器將“.class”字節碼文件中的類給加載帶JVM中,最後就是JVM執行寫好的代碼。執行過程如下圖

類加載器

類加載過程

加載 -> 驗證 -> 準備 -> 解析 -> 初始化 -> 使用 -> 卸載

加載

一旦JVM進程啟動之後,一定會先把類加載到內存中,然後從main()方法的入口代碼開始執行

public class HelloWorld {
    public static void main(String[] args) {
        
    }
}

加載指是將類CLass文件讀入到內存中,並創建一個java.lang.Class對象

類的加載器由類加載器完成,類加載器通常由JVM提供,除此之外,可以通過繼承ClassLoader基類來創建自己的類加載器

通過使用不同的類加載器,可以從不同來源加載類的二進制數據

  • 從本地文件系統加載class文件
  • 從JAR包加載class文件,JDBC編程時用到的數據庫驅動類就放在JAR文件中,JVM可以從JAR文件中直接加載該class文件
  • 通過網絡加載class文件
  • 把一個java源文件動態編譯,並執行加載

準備階段

驗證

根據Java虛擬機規范,來校驗加載進來的“.class”文件中的內容,是否符合指定的規范,如果“.class”文件被人篡改,裡面的字節碼不符合規范,那麼JVM是沒法執行這個字節碼。所在把“.class”文件加載到內存後,必須先驗證一下,檢驗他必須完全符合JVM規范,才交給JVM來運行。其主要包括四種驗證,文件格式驗證,元數據驗證,字節碼驗證,符號引用驗證

  • 文件格式驗證:主要驗證字節流是否符合Class文件格式規范,並且能被當前的虛擬機加載處理。例如:主,次版本號是否在當前虛擬機處理的范圍之內。常量池中是否有不被支持的常量類型。指向常量的中的索引值是否存在不存在的常量或不符合類型的常量
  • 元數據驗證:對字節碼描述的信息進行語義的分析,分析是否符合java的語言語法的規范
  • 字節碼驗證:最重要的驗證環節,分析數據流和控制,確定語義是合法的,符合邏輯的。主要的針對元數據驗證後對方法體的驗證。保證類方法在運行時不會有危害出現
  • 符號引用驗證:主要是針對符號引用轉換為直接引用的時候,是會延伸到第三解析階段,主要去確定訪問類型等涉及到引用的情況,主要是要保證引用一定會被訪問到,不會出現類等無法訪問的問題

準備

給引用的類分配一定的內存空間,然後給裡面的類變量(也就是static修飾的變量)分配內存空間,來一個默認的初始值

解析

實際上是把將類的二進制數據中的符號引用替換成直接引用的過程,這個部分的內容比較復雜,設計到JVM的底層

  • 符號引用:符號引用是以一組符號來描述所引用的目標,符號可以是任何的字面形式的字面量,隻要不會出現沖突能夠定位到就行。佈局和內存無關
  • 直接引用:是指向目標的指針,偏移量或者能夠直接定位的句柄。該引用是和內存中的佈局有關的,並且一定加載進來的

解析主要包括:

  • 類或接口的解析
  • 字段解析
  • 類方法解析
  • 接口方法解析

初始化

初始化是為類的靜態變量賦予正確的初始值,準備階段和初始化階段看似有點矛盾,其實是不矛盾的,如果類中有語句:private static int a = 10,它的執行過程是這樣的,首先字節碼文件被加載到內存後,先進行鏈接的驗證這一步驟,驗證通過後準備階段,給a分配內存,因為變量a是static的,所以此時a等於int類型的默認初始值0,即a=0,然後到解析(後面在說),到初始化這一步驟時,才把a的真正的值10賦給a,此時a=10

一個非常重要的規則,就是如果初始化一個類的時候,發現他的父類還沒初始化,那麼必須先初始化他的父類

加載器

啟動類/根類加載器

Bootstrap ClassLoader,他主要是負責加載我們在機器上安裝的Java目錄下的核心類的,是用原生代碼來實現的,並不繼承自 java.lang.ClassLoader(負責加載$JAVA_HOME中jre/lib/rt.jar裡所有的class,由C++實現,不是ClassLoader子類)。由於引導類加載器涉及到虛擬機本地實現細節,開發者無法直接獲取到啟動類加載器的引用,所以不允許直接通過引用進行操作

擴展類加載器

Extension ClassLoader,負責加載JRE的擴展目錄,lib/ext或者由java.ext.dirs系統屬性指定的目錄中的JAR包的類。由Java語言實現,父類加載器為null

應用程序類加載器

Application ClassLoader,被稱為應用(也稱為系統)類加載器,它負責在JVM啟動時加載來自Java命令的-classpath選項、java.class.path系統屬性,或者CLASSPATH換將變量所指定的JAR包和類路徑。程序可以通過ClassLoader的靜態方法getSystemClassLoader()來獲取系統類加載器。如果沒有特別指定,則用戶自定義的類加載器都以此類加載器作為父加載器。由Java語言實現,父類加載器為ExtClassLoader。

類加載器加載Class大致要經過如下8個步驟:

  1. 檢測此Class是否載入過,即在緩沖區中是否有此Class,如果有直接進入第8步,否則進入第2步
  2. 如果沒有父類加載器,則要麼Parent是根類加載器,要麼本身就是根類加載器,則跳到第4步,如果父類加載器存在,則進入第3步
  3. 請求使用父類加載器去載入目標類,如果載入成功則跳至第8步,否則接著執行第5步
  4. 請求使用根類加載器去載入目標類,如果載入成功則跳至第8步,否則跳至第7步
  5. 當前類加載器嘗試尋找Class文件,如果找到則執行第6步,如果找不到則執行第7步
  6. 從文件中載入Class,成功後跳至第8步
  7. 拋出ClassNotFountException異常
  8. 返回對應的java.lang.Class對象

自定義類加載器

除瞭上面那幾種之外,還可以自定義類加載器,去根據你自己的需求加載你的類

類加載機制

JVM的類加載機制主要有如下3種:

  • 全盤負責:所謂全盤負責,就是當一個類加載器負責加載某個Class時,該Class所依賴和引用其他Class也將由該類加載器負責載入,除非顯示使用另外一個類加載器來載入
  • 雙親委派:所謂的雙親委派,則是先讓父類加載器試圖加載該Class,隻有在父類加載器無法加載該類時才嘗試從自己的類路徑中加載該類。通俗的講,就是某個特定的類加載器在接到加載類的請求時,首先將加載任務委托給父加載器,依次遞歸,如果父加載器可以完成類加載任務,就成功返回;隻有父加載器無法完成此加載任務時,才自己去加載
  • 緩存機制。緩存機制將會保證所有加載過的Class都會被緩存,當程序中需要使用某個Class時,類加載器先從緩存區中搜尋該Class,隻有當緩存區中不存在該Class對象時,系統才會讀取該類對應的二進制數據,並將其轉換成Class對象,存入緩沖區中。這就是為很麼修改瞭Class後,必須重新啟動JVM,程序所做的修改才會生效的原因

雙親委派機制

這就是所謂的**雙親委派模型:**先找父親去加載,不行的話再由兒子來加載

  • 雙親委派機制,其工作原理的是,如果一個類加載器收到瞭類加載請求,它並不會自己先去加載,而是把這個請求委托給父類的加載器去執行,如果父類加載器還存在其父類加載器,則進一步向上委托,依次遞歸,請求最終將到達頂層的啟動類加載器,如果父類加載器可以完成類加載任務,就成功返回,倘若父類加載器無法完成此加載任務,子加載器才會嘗試自己去加載,這就是雙親委派模式,即每個兒子都很懶,每次有活就丟給父親去幹,直到父親說這件事我也幹不瞭時,兒子自己才想辦法去完成
  • 雙親委派機制的優勢:采用雙親委派模式的是好處是Java類隨著它的類加載器一起具備瞭一種帶有優先級的層次關系,通過這種層級關可以避免類的重復加載,當父親已經加載瞭該類時,就沒有必要子ClassLoader再加載一次。其次是考慮到安全因素,java核心api中定義類型不會被隨意替換,假設通過網絡傳遞一個名為java.lang.Integer的類,通過雙親委托模式傳遞到啟動類加載器,而啟動類加載器在核心Java API發現這個名字的類,發現該類已被加載,並不會重新加載網絡傳遞的過來的java.lang.Integer,而直接返回已加載過的Integer.class,這樣便可以防止核心API庫被隨意篡改

總結

到此這篇關於jvm運行原理以及類加載器的文章就介紹到這瞭,更多相關jvm運行原理及類加載器內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: