1. 程式人生 > >類的加載機制

類的加載機制

url 觸發 ima 沒有初始化 編譯 卸載 類加載機制 uip 存儲結構

一.目標:

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指令是程序被編譯後,存放於類構造器<client>方法之中.特例:

public static final int value = 123;
?

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

解析:

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

  • 符號引用與虛擬機實現的布局無關,引用的目標並不一定要已經加載到內存中。各種虛擬機實現的內存布局可以各不相同,但是它們能接受的符號引用必須是一致的,因為符號引用的字面量形式明確定義在Java虛擬機規範的Class文件格式中。
  • 直接引用可以是指向目標的指針,相對偏移量或是一個能間接定位到目標的句柄。如果有了直接引用,那引用的目標必定已經在內存中存在

主要有以下四種:

  1. 類或接口的解析
  2. 字段解析
  3. 類方法解析
  4. 接口方法解析

初始化:

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

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類型體系中最基礎的行為也就無法保證.應用程序也將會一片混亂.

類的加載機制