ClassLoader

類加載器

類加載的機制的層次結構

每一個編寫的」.java」拓展名類文件都存儲着須要執行的程序邏輯,這些」.java」文件通過Java編譯器編譯成拓展名爲」.class」的文件,」.class」文件中保存着Java代碼經轉換後的虛擬機指令,當須要使用某個類時,虛擬機將會加載它的」.class」文件,並建立對應的class對象,將class文件加載到虛擬機的內存,這個過程稱爲類加載,這裏咱們須要瞭解一下類加載的過程,以下:java

Jvm執行class文件api

步驟1、類加載機制

將class文件字節碼內容加載到內存中,並將這些靜態數據轉換成方法區中的運行時數據結構,在堆中生成一個表明這個類的java.lang.Class對象,做爲方法區類數據的訪問入口,這個過程須要類加載器參與。緩存

當系統運行時,類加載器將.class文件的二進制數據從外部存儲器(如光盤,硬盤)調入內存中,CPU再從內存中讀取指令和數據進行運算,並將運算結果存入內存中。內存在該過程當中充當着"二傳手"的做用,通俗的講,若是沒有內存,類加載器從外部存儲設備調入.class文件二進制數據直接給CPU處理,而因爲CPU的處理速度遠遠大於調入數據的速度,容易形成數據的脫節,因此須要內存起緩衝做用。安全

類將.class文件加載至運行時的方法區後,會在堆中建立一個Java.lang.Class對象,用來封裝類位於方法區內的數據結構,該Class對象是在加載類的過程當中建立的,每一個類都對應有一個Class類型的對象,Class類的構造方法是私有的,只有JVM可以建立。所以Class對象是反射的入口,使用該對象就能夠得到目標類所關聯的.class文件中具體的數據結構。網絡

 

 

 

類加載的最終產物就是位於堆中的Class對象(注意不是目標類對象),該對象封裝了類在方法區中的數據結構,而且向用戶提供了訪問方法區數據結構的接口,即Java反射的接口。數據結構

 

 

步驟2、鏈接過程

將java類的二進制代碼合併到JVM的運行狀態之中的過程多線程

驗證:確保加載的類信息符合JVM規範,沒有安全方面的問題jvm

準備:正式爲類變量(static變量)分配內存並設置類變量初始值的階段,這些內存都將在方法區中進行分配測試

解析:虛擬機常量池的符號引用替換爲字節引用過程this

步驟3、初始化

初始化階段是執行類構造器<clinit>()方法的過程。類構造器<clinit>()方法是由編譯器自動收藏類中的全部類變量的賦值動做和靜態語句塊(static塊)中的語句合併產生,代碼從上往下執行。

當初始化一個類的時候,若是發現其父類尚未進行過初始化,則須要先觸發其父類的初始化

虛擬機會保、、、證一個類的<clinit>()方法在多線程環境中被正確加鎖和同步

當範圍一個Java類的靜態域時,只有真正聲名這個域的類纔會被初始化

 

 

類加載器的層次結構

 

啓動(Bootstrap)類加載器

擴展(Extension)類加載器

系統(-)類加載器

 

啓動(Bootstrap)類加載器

啓動類加載器主要加載的是JVM自身須要的類,這個類加載使用C++語言實現的,是虛擬機自身的一部分,它負責將 <JAVA_HOME>/lib路徑下的核心類庫或-Xbootclasspath參數指定的路徑下的jar包加載到內存中,注意必因爲虛擬機是按照文件名識別加載jar包的,如rt.jar,若是文件名不被虛擬機識別,即便把jar包丟到lib目錄下也是沒有做用的(出於安全考慮,Bootstrap啓動類加載器只加載包名爲java、javax、sun等開頭的類)。

擴展(Extension)類加載器

擴展類加載器是指Sun公司(已被Oracle收購)實現的sun.misc.Launcher$ExtClassLoader類,由Java語言實現的,是Launcher的靜態內部類,它負責加載<JAVA_HOME>/lib/ext目錄下或者由系統變量-Djava.ext.dir指定位路徑中的類庫,開發者能夠直接使用標準擴展類加載器。

系統(System)類加載器、

也稱應用程序加載器是指 Sun公司實現的sun.misc.Launcher$AppClassLoader。它負責加載系統類路徑java -classpath或-D java.class.path 指定路徑下的類庫,也就是咱們常常用到的classpath路徑,開發者能夠直接使用系統類加載器,通常狀況下該類加載是程序中默認的類加載器,經過ClassLoader#getSystemClassLoader()方法能夠獲取到該類加載器。

  在Java的平常應用程序開發中,類的加載幾乎是由上述3種類加載器相互配合執行的,在必要時,咱們還能夠自定義類加載器,須要注意的是,Java虛擬機對class文件採用的是按需加載的方式,也就是說當須要使用該類時纔會將它的class文件加載到內存生成class對象,並且加載某個類的class文件時,Java虛擬機採用的是雙親委派模式即把請求交由父類處理,它一種任務委派模式,下面咱們進一步瞭解它。

理解雙親委派模式

採用雙親委派模式的是好處是Java類隨着它的類加載器一塊兒具有了一種帶有優先級的層次關係,經過這種層級關能夠避免類的重複加載,當父親已經加載了該類時,就沒有必要子ClassLoader再加載一次。其次是考慮到安全因素,java核心api中定義類型不會被隨意替換,假設經過網絡傳遞一個名爲java.lang.Integer的類,經過雙親委託模式傳遞到啓動類加載器,而啓動類加載器在覈心Java API發現這個名字的類,發現該類已被加載,並不會從新加載網絡傳遞的過來的java.lang.Integer,而直接返回已加載過的Integer.class,這樣即可以防止核心API庫被隨意篡改。可能你會想,若是咱們在classpath路徑下自定義一個名爲java.lang.SingleInterge類(該類是胡編的)呢?該類並不存在java.lang中,通過雙親委託模式,傳遞到啓動類加載器中,因爲父類加載器路徑下並無該類,因此不會加載,將反向委託給子類加載器加載,最終會經過系統類加載器加載該類。可是這樣作是不容許,由於java.lang是核心API包,須要訪問權限,強制加載將會報出以下異常

java.lang.SecurityException: Prohibited package name: java.lang

 

因此不管如何都沒法加載成功的。下面咱們從代碼層面瞭解幾個Java中定義的類加載器及其雙親委派模式的實現,它們類圖關係以下

 

雙親委派模式是在Java 1.2後引入的,其工做原理的是,若是一個類加載器收到了類加載請求,它並不會本身先去加載,而是把這個請求委託給父類的加載器去執行,若是父類加載器還存在其父類加載器,則進一步向上委託,依次遞歸,請求最終將到達頂層的啓動類加載器,若是父類加載器能夠完成類加載任務,就成功返回,假若父類加載器沒法完成此加載任務,子加載器纔會嘗試本身去加載,這就是雙親委派模式,即每一個兒子都很懶,每次有活就丟給父親去幹,直到父親說這件事我也幹不了時,兒子本身想辦法去完成,這不就是傳說中的實力坑爹啊?那麼採用這種模式有啥用呢?

雙親委派模式優點

採用雙親委派模式的是好處是Java類隨着它的類加載器一塊兒具有了一種帶有優先級的層次關係,經過這種層級關能夠避免類的重複加載,當父親已經加載了該類時,就沒有必要子ClassLoader再加載一次。其次是考慮到安全因素,java核心api中定義類型不會被隨意替換,假設經過網絡傳遞一個名爲java.lang.Integer的類,經過雙親委託模式傳遞到啓動類加載器,而啓動類加載器在覈心Java API發現這個名字的類,發現該類已被加載,並不會從新加載網絡傳遞的過來的java.lang.Integer,而直接返回已加載過的Integer.class,這樣即可以防止核心API庫被隨意篡改。可能你會想,若是咱們在classpath路徑下自定義一個名爲java.lang.SingleInterge類(該類是胡編的)呢?該類並不存在java.lang中,通過雙親委託模式,傳遞到啓動類加載器中,因爲父類加載器路徑下並無該類,因此不會加載,將反向委託給子類加載器加載,最終會經過系統類加載器加載該類。可是這樣作是不容許,由於java.lang是核心API包,須要訪問權限,強制加載將會報出以下異常

類加載器間的關係

咱們進一步瞭解類加載器間的關係(並不是指繼承關係),主要能夠分爲如下4點

啓動類加載器,由C++實現,沒有父類。

拓展類加載器(ExtClassLoader),由Java語言實現,父類加載器爲null

系統類加載器(AppClassLoader),由Java語言實現,父類加載器爲ExtClassLoader

自定義類加載器,父類加載器確定爲AppClassLoader。

類加載器經常使用方法

loadClass(String)

該方法加載指定名稱(包括包名)的二進制類型,該方法在JDK1.2以後再也不建議用戶重寫但用戶能夠直接調用該方法,loadClass()方法是ClassLoader類本身實現的,該方法中的邏輯就是雙親委派模式的實現,其源碼以下,loadClass(String name, boolean resolve)是一個重載方法,resolve參數表明是否生成class對象的同時進行解析相關操做。

正如loadClass方法所展現的,當類加載請求到來時,先從緩存中查找該類對象,若是存在直接返回,若是不存在則交給該類加載去的父加載器去加載,假若沒有父加載則交給頂級啓動類加載器去加載,最後假若仍沒有找到,則使用findClass()方法去加載(關於findClass()稍後會進一步介紹)。從loadClass實現也能夠知道若是不想從新定義加載類的規則,也沒有複雜的邏輯,只想在運行時加載本身指定的類,那麼咱們能夠直接使用this.getClass().getClassLoder.loadClass("className"),這樣就能夠直接調用ClassLoader的loadClass方法獲取到class對象。

findClass(String)

在JDK1.2以前,在自定義類加載時,總會去繼承ClassLoader類並重寫loadClass方法,從而實現自定義的類加載類,可是在JDK1.2以後已再也不建議用戶去覆蓋loadClass()方法,而是建議把自定義的類加載邏輯寫在findClass()方法中,從前面的分析可知,findClass()方法是在loadClass()方法中被調用的,當loadClass()方法中父加載器加載失敗後,則會調用本身的findClass()方法來完成類加載,這樣就能夠保證自定義的類加載器也符合雙親委託模式。須要注意的是ClassLoader類中並無實現findClass()方法的具體代碼邏輯,取而代之的是拋出ClassNotFoundException異常,同時應該知道的是findClass方法一般是和defineClass方法一塊兒使用的(稍後會分析)

defineClass(byte[] b, int off, int len)

defineClass()方法是用來將byte字節流解析成JVM可以識別的Class對象(ClassLoader中已實現該方法邏輯),經過這個方法不只可以經過class文件實例化class對象,也能夠經過其餘方式實例化class對象,如經過網絡接收一個類的字節碼,而後轉換爲byte字節流建立對應的Class對象,defineClass()方法一般與findClass()方法一塊兒使用,通常狀況下,在自定義類加載器時,會直接覆蓋ClassLoader的findClass()方法並編寫加載規則,取得要加載類的字節碼後轉換成流,而後調用defineClass()方法生成類的Class對象

resolveClass(Class≺?≻ c)

使用該方法可使用類的Class對象建立完成也同時被解析。前面咱們說連接階段主要是對字節碼進行驗證,爲類變量分配內存並設置初始值同時將字節碼文件中的符號引用轉換爲直接引用。

 

Bootstrap 底層是C++  識別系統的rt.jar包 加載核心的類   

任何類加載器先交給父類進行加載,若是父類加載失敗,再交給子類執行

代碼解釋雙親委派模式:

沒有用原生JDK的String

 

包名和類名字:

咱們這麼定義String類代碼:

package java.lang;

public class String {
  public String toString(){
      return toString();
  }
}

 

使用測試:

package com.toov5.jvm;

public class TestJvm01 {
   public static void main(String[] args) {
     String aString = "a";
     System.out.println(aString);
}
  
}

結果:

包名類名都同樣

 咱們看下它的ClassLoader

package com.toov5.test;

public class Test01 {


       public static void main(String[] args) {
         String aString = "a";
         System.out.println(aString);
         System.out.println(Test01.class.getClassLoader());
         System.out.println(aString.getClass().getClassLoader());
      
    }    
}

Test01是Application 

String是 Bootstrap

 先去讓父類去執行加載的 

相關文章
相關標籤/搜索