深入理解JVM虛擬機器6:深入理解JVM類載入機制

Java技術江湖發表於2019-11-13

本文轉自網際網路,侵刪

本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到我的倉庫裡檢視

https://github.com/h2pl/Java-Tutorial

喜歡的話麻煩點下Star哈

文章將同步到我的個人部落格:

www.how2playlife.com

本文是微信公眾號【Java技術江湖】的《深入理解JVM虛擬機器》其中一篇,本文部分內容來源於網路,為了把本文主題講得清晰透徹,也整合了很多我認為不錯的技術部落格內容,引用其中了一些比較好的部落格文章,如有侵權,請聯絡作者。

該系列博文會告訴你如何從入門到進階,一步步地學習JVM基礎知識,並上手進行JVM調優實戰,JVM是每一個Java工程師必須要學習和理解的知識點,你必須要掌握其實現原理,才能更完整地瞭解整個Java技術體系,形成自己的知識框架。

為了更好地總結和檢驗你的學習成果,本系列文章也會提供每個知識點對應的面試題以及參考答案。

如果對本系列文章有什麼建議,或者是有什麼疑問的話,也可以關注公眾號【Java技術江湖】聯絡作者,歡迎你參與本系列博文的創作和修訂。

一.目標:

1.什麼是類的載入?

2.類的生命週期?

3.類載入器是什麼?

4.雙親委派機制是什麼?

二.原理 (類的載入過程及其最終產品):

JVM將class檔案位元組碼檔案載入到記憶體中, 並將這些靜態資料轉換成方法區中的執行時資料結構,在堆(並不一定在堆中,HotSpot在方法區中)中生成一個代表這個類的java.lang.Class 物件,作為方法區類資料的訪問入口。

三.過程(類的生命週期):

JVM類載入機制分為五個部分:載入,驗證,準備,解析,初始化,下面我們就分別來看一下這五個過程。其中載入、檢驗、準備、初始化和解除安裝這個五個階段的順序是固定的,而解析則未必。為了支援動態繫結,解析這個過程可以發生在初始化階段之後。

載入:

載入過程主要完成三件事情:

  1. 通過類的全限定名來獲取定義此類的二進位制位元組流
  2. 將這個類位元組流代表的靜態儲存結構轉為方法區的執行時資料結構
  3. 在堆中生成一個代表此類的java.lang.Class物件,作為訪問方法區這些資料結構的入口。

這個過程主要就是類載入器完成。

校驗:

此階段主要確保Class檔案的位元組流中包含的資訊符合當前虛擬機器的要求,並且不會危害虛擬機器的自身安全。

  1. 檔案格式驗證:基於位元組流驗證。
  2. 後設資料驗證:基於 方法區的儲存結構驗證。
  3. 位元組碼驗證:基於方法區的儲存結構驗證。
  4. 符號引用驗證:基於方法區的儲存結構驗證。

準備:

為類變數分配記憶體,並將其初始化為預設值。(此時為預設值,在初始化的時候才會給變數賦值)即在方法區中分配這些變數所使用的記憶體空間。例如:

public static int value = 123;

此時在準備階段過後的初始值為0而不是123;將value賦值為123的putstatic指令是程式被編譯後,存放於類構造器 方法之中.特例:

public static final int value = 123;

此時value的值在準備階段過後就是123。

解析:

把型別中的符號引用轉換為直接引用。

  • 符號引用與虛擬機器實現的佈局無關,引用的目標並不一定要已經載入到記憶體中。各種虛擬機器實現的記憶體佈局可以各不相同,但是它們能接受的符號引用必須是一致的,因為符號引用的字面量形式明確定義在Java虛擬機器規範的Class檔案格式中。
  • 直接引用可以是指向目標的指標,相對偏移量或是一個能間接定位到目標的控制程式碼。如果有了直接引用,那引用的目標必定已經在記憶體中存在

主要有以下四種:

  1. 類或介面的解析
  2. 欄位解析
  3. 類方法解析
  4. 介面方法解析

初始化:

初始化階段是執行類構造器 方法的過程。 方法是由編譯器自動收集類中的類變數的賦值操作和靜態語句塊中的語句合併而成的。虛擬機器會保證 方法執行之前,父類的 方法已經執行完畢。如果一個類中沒有對靜態變數賦值也沒有靜態語句塊,那麼編譯器可以不為這個類生成 ()方法。

java中,對於初始化階段,有且只有以下五種情況才會對要求類立刻“初始化”(載入,驗證,準備,自然需要在此之前開始):

  1. 使用new關鍵字例項化物件、訪問或者設定一個類的靜態欄位(被final修飾、編譯器優化時已經放入常量池的例外)、呼叫類方法,都會初始化該靜態欄位或者靜態方法所在的類。
  2. 初始化類的時候,如果其父類沒有被初始化過,則要先觸發其父類初始化。
  3. 使用java.lang.reflect包的方法進行反射呼叫的時候,如果類沒有被初始化,則要先初始化。
  4. 虛擬機器啟動時,使用者會先初始化要執行的主類(含有main)
  5. jdk 1.7後,如果java.lang.invoke.MethodHandle的例項最後對應的解析結果是 REF_getStatic、REF_putStatic、REF_invokeStatic方法控制程式碼,並且這個方法所在類沒有初始化,則先初始化。

四.類載入器:

把類載入階段的“通過一個類的全限定名來獲取描述此類的二進位制位元組流”這個動作交給虛擬機器之外的類載入器來完成。這樣的好處在於,我們可以自行實現類載入器來載入其他格式的類,只要是二進位制位元組流就行,這就大大增強了載入器靈活性。系統自帶的類載入器分為三種:

  1. 啟動類載入器。
  2. 擴充套件類載入器。
  3. 應用程式類載入器。

五.雙親委派機制:

雙親委派機制工作過程:

如果一個類載入器收到了類載入器的請求.它首先不會自己去嘗試載入這個類.而是把這個請求委派給父載入器去完成.每個層次的類載入器都是如此.因此所有的載入請求最終都會傳送到Bootstrap類載入器(啟動類載入器)中.只有父類載入反饋自己無法載入這個請求(它的搜尋範圍中沒有找到所需的類)時.子載入器才會嘗試自己去載入。

雙親委派模型的優點:java類隨著它的載入器一起具備了一種帶有優先順序的層次關係.

例如類java.lang.Object,它存放在rt.jart之中.無論哪一個類載入器都要載入這個類.最終都是雙親委派模型最頂端的Bootstrap類載入器去載入.因此Object類在程式的各種類載入器環境中都是同一個類.相反.如果沒有使用雙親委派模型.由各個類載入器自行去載入的話.如果使用者編寫了一個稱為“java.lang.Object”的類.並存放在程式的ClassPath中.那系統中將會出現多個不同的Object類.java型別體系中最基礎的行為也就無法保證.應用程式也將會一片混亂.

參考文章

https://segmentfault.com/a/1190000009707894

https://www.cnblogs.com/hysum/p/7100874.html

http://c.biancheng.net/view/939.html

https://www.runoob.com/

https://blog.csdn.net/android_hl/article/details/53228348

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69951287/viewspace-2664029/,如需轉載,請註明出處,否則將追究法律責任。

相關文章