天天看點

類加載機制-深入了解jvm

一.目标:

1.什麼是類的加載?

2.類的生命周期?

3.類加載器是什麼?

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

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

JVM将class檔案位元組碼檔案加載到記憶體中, 并将這些靜态資料轉換成方法區中的運作時資料結構,在堆(并不一定在堆中,HotSpot在方法區中)中生成一個代表這個類的java.lang.Class 對象,作為方法區類資料的通路入口。

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

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

類加載機制-深入了解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. 應用程式類加載器。

四.雙親委派機制:

類加載機制-深入了解jvm

雙親委派機制工作過程:

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

雙親委派模型的優點:java類随着它的加載器一起具備了一種帶有優先級的層次關系.

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

類加載機制-深入了解jvm