1. 程式人生 > >深入理解Java類加載器(1)

深入理解Java類加載器(1)

介紹 生命 等等 inf java_home col 原因 就是 校驗

類加載器概述:

  java類的加載是由虛擬機來完成的,虛擬機把描述類的Class文件加載到內存,並對數據進行校驗,解析和初始化,最終形成能被java虛擬機直接使用的java類型,這就是虛擬機的類加載機制.JVM中用來完成上述功能的具體實現就是類加載器.類加載器讀取.class字節碼文件將其轉換成java.lang.Class類的一個實例.每個實例用來表示一個java類.通過該實例的newInstance()方法可以創建出一個該類的對象.

類的生命周期:

  類從加載到虛擬機內存到被從內存中釋放,經歷的生命周期如下:

技術分享圖片

加載:"加載"是"類加載"過程的一個階段,此階段完成的功能是:

  通過類的全限定名來獲取定義此類的二進制字節流

  將此二進制字節流所代表的靜態存儲結構轉化成方法區的運行時數據結構

  在內存中生成代表此類的java.lang.Class對象,作為該類訪問入口.

驗證:連接階段第一步.驗證的目的是確保Class文件的字節流中信息符合虛擬機的要求,不會危害虛擬機安全,使得虛擬機免受惡意代碼的攻擊.大致完成以下四個校驗動作:

  文件格式驗證

  源數據驗證

  字節碼驗證

  符號引用驗證

準備:連接階段第二步,正式為類變量分配內存並設置變量的初始值.(僅包含類變量,不包含實例變量).  

解析:連接階段第三步,虛擬機將常量池中的符號引用替換為直接引用,解析動作主要針對類或接口,字段,類方法,方法類型等等..

初始化:類的初始化是類加載過程的最後一步,在該階段,才真正意義上的開始執行類中定義的java程序代碼.該階段會執行類構造器.

使用:使用該類所提供的功能.

卸載:從內存中釋放.

獲取Class文件途徑:

  java類可以動態被加載到內存,這是java的一大特點,也稱為運行時綁定,或動態綁定.

     1.從ZIP包中讀取,很常見,最終成為日後JAR,WAR,EAR格式的基礎.

     2.從網絡中獲取,這種場景典型的就是Applet.

     3.運行時計算生成,典型的情景就是java動態代理技術.

     4.從其他文件中生成,典型場景是JSP應用,即由JSP文件生成對應的Class類.

java.lang.ClassLoader類概述:

  中文文檔中對ClassLoader類的定義如下:

   技術分享圖片

   從文檔中對ClassLoader類的介紹可以總結出這個類的作用就是根據一個指定的類的全限定名,找到對應的Class字節碼文件,然後加載它轉化成一個java.lang.Class類的一個實例.

類加載器的劃分:

   大部分java程序會使用以下3中系統提供的類加載器:

   啟動類加載器(Bootstrap ClassLoader):

    這個類加載器負責將<JAVA_HOME>\lib目錄下的類庫加載到虛擬機內存中,用來加載java的核心庫,此類加載器並不繼承於java.lang.ClassLoader,不能被java程序直接調用,代碼是使用C++編寫的.是虛擬機自身的一部分.

   擴展類加載器(Extendsion ClassLoader):
   
 這個類加載器負責加載<JAVA_HOME>\lib\ext目錄下的類庫,用來加載java的擴展庫,開發者可以直接使用這個類加載器.

   應用程序類加載器(Application ClassLoader):

    這個類加載器負責加載用戶類路徑(CLASSPATH)下的類庫,一般我們編寫的java類都是由這個類加載器加載,這個類加載器是CLassLoader中的getSystemClassLoader()方法的返回值,所以也稱為系統類加載器.一般情況下這就是系統默認的類加載器.

  除此之外,我們還可以加入自己定義的類加載器,以滿足特殊的需求,需要繼承java.lang.ClassLoader類.

  類加載器之間的層次關系如下圖:

   技術分享圖片

使用代碼觀察一下類加載器:

package com.wang.test;

public class TestClassLoader {

    public static void main(String[] args) {
        ClassLoader loader = TestClassLoader.class.getClassLoader();
        System.out.println(loader.toString());
        System.out.println(loader.getParent().toString());
        System.out.println(loader.getParent().getParent());
    }
}

觀察打印結果:

sun.misc.Launcher$AppClassLoader@500c05c2
sun.misc.Launcher$ExtClassLoader@454e2c9c
null

  第一行打印的是應用程序類加載器(默認加載器),第二行打印的是其父類加載器,擴展類加載器,按照我們的想法第三行應該打印啟動類加載器的,這裏卻返回的null,原因是getParent(),返回時null的話,就默認使用啟動類加載器作為父加載器.

類加載器的雙親委派模型:

  雙親委派模型是一種組織類加載器之間關系的一種規範,他的工作原理是:如果一個類加載器收到了類加載的請求,它不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,這樣層層遞進,最終所有的加載請求都被傳到最頂層的啟動類加載器中,只有當父類加載器無法完成這個加載請求(它的搜索範圍內沒有找到所需的類)時,才會交給子類加載器去嘗試加載.

  這樣的好處是:java類隨著它的類加載器一起具備了帶有優先級的層次關系.這是十分必要的,比如java.langObject,它存放在\jre\lib\rt.jar中,它是所有java類的父類,因此無論哪個類加載都要加載這個類,最終所有的加載請求都匯總到頂層的啟動類加載器中,因此Object類會由啟動類加載器來加載,所以加載的都是同一個類,如果不使用雙親委派模型,由各個類加載器自行去加載的話,系統中就會出現不止一個Object類,應用程序就會全亂了.

Class.forname()與ClassLoader.loadClass():

  Class.forname():是一個靜態方法,最常用的是Class.forname(String className);根據傳入的類的全限定名返回一個Class對象.該方法在將Class文件加載到內存的同時,會執行類的初始化.

  如: Class.forName("com.wang.HelloWorld");

  ClassLoader.loadClass():這是一個實例方法,需要一個ClassLoader對象來調用該方法,該方法將Class文件加載到內存時,並不會執行類的初始化,直到這個類第一次使用時才進行初始化.該方法因為需要得到一個ClassLoader對象,所以可以根據需要指定使用哪個類加載器.

  如:ClassLoader cl=.......;cl.loadClass("com.wang.HelloWorld");

深入理解Java類加載器(1)