類加載子系統

一、作用:

  1.加載class文件,class文件開頭具有特定的文件標識

  2.只負責加載,能否運行由Execution Engine決定

  3.信息存放位置在方法區

二、ClassLoader(類加載器)

  1.classfile存放在本地內存上,執行時需要加載到JVM中,根據這個文件實例出n個一模一樣的實例
  2.classfile加載到JVM中,被稱為DNA元數據模板,放在方法區
  3.在.class文件->JVM->最終成為元數據模板,此過程就要一個運輸工具(ClassLoader),扮演一個快遞員的角色

三、類加載過程

   1.執行
  2.加載、鏈接、初始化
  3.加載失敗拋出異常

 

四、加載階段

   1.通過類的全限定名稱獲取類的二進制位元組流
  2.將這個位元組流所代表的靜態儲存結構轉換成方法區的運行時數據結構
  3.在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口

五、鏈接階段(驗證->準備->解析)

  1.驗證:保證正確性(CAFEBABE)與安全性


  2.驗證方式:文件格式、元數據、位元組碼、符號引用驗證
  3.準備:
    a.為static變量分配內存與初始默認值
    b.不包含final修飾的static,因為final在編譯時就分配好了默認值,準備階段會顯示初始化
    c.不會為實例對象分配初始化
  4.解析:
    a.將常量池的符號引用轉換為直接引用的過程
    b.解析操作往往會隨初始化之後再執行
    c.符號引用就是一組符號來描述所引用的目標;直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的語柄
    d.主要針對類接子方等類型

六、初始化階段

  1.類的初始化時機

 2.創建類的實例

 3.靜態

 4.反射

 5.初始化一個類的子類

 6.Java虛擬機啟動時被標明為啟動類的類

七、clinit()

  1.初始化階段就是執行類構造器方法<clinit>()的過程

  2.當我們代碼中包含static變量的時候,就會有clinit方法

 3.<clinit>()方法中的指令按語句在源文件中出現的順序執行

 4.<clinit>()不同於類的構造器。(關聯:構造器是虛擬機視角下的<init>()

  5.若該類具有父類,JVM會保證子類的<clinit>()執行前,父類的<clinit>()已經執行完畢

  6.虛擬機必須保證一個類的<clinit>()方法在多線程下被同步加鎖

八、類加載器的分類

  概述

    1.JVM嚴格來講支持兩種類型的類加載器 。分別為引導類加載器(Bootstrap ClassLoader)和自定義類加載器(User-Defined ClassLoader)

    2.Java虛擬機規範將所有派生於抽象類ClassLoader的類加載器都劃分為自定義類加載器

    3.無論類加載器的類型如何劃分,在程序中我們最常見的類加載器始終只有3個,如下所示

ExtClassLoader

AppClassLoader

public class ClassLoaderTest {
    public static void main(String[] args) {

        //獲取系統類加載器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //獲取其上層:擴展類加載器
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d

        //獲取其上層:獲取不到引導類加載器
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println(bootstrapClassLoader);//null

        //對於用戶自定義類來說:默認使用系統類加載器進行加載
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //String類使用引導類加載器進行加載的。---> Java的核心類庫都是使用引導類加載器進行加載的。
        ClassLoader classLoader1 = String.class.getClassLoader();
        System.out.println(classLoader1);//null


    }
}

  • 我們嘗試獲取引導類加載器,獲取到的值為 null ,這並不代表引導類加載器不存在,因為引導類加載器右 C/C++ 語言,我們獲取不到
  • 兩次獲取系統類加載器的值都相同:sun.misc.Launcher$AppClassLoader@18b4aac2 ,這說明系統類加載器是全局唯一的

九、虛擬機自帶的加載器

  1.啟動類加載器

啟動類加載器(引導類加載器,Bootstrap ClassLoader)

a.這個類加載使用C/C++語言實現的,嵌套在JVM內部

b.它用來加載Java的核心庫(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路徑下的內容),用於提供JVM自身需要的類

c.並不繼承自java.lang.ClassLoader,沒有父加載器

d.加載擴展類和應用程序類加載器,並作為他們的父類加載器

e.出於安全考慮,Bootstrap啟動類加載器只加載包名為java、javax、sun等開頭的類

  2.擴展類加載器

擴展類加載器(Extension ClassLoader)

a.Java語言編寫,由sun.misc.Launcher$ExtClassLoader實現

b.派生於ClassLoader類

c.父類加載器為啟動類加載器

d.從java.ext.dirs系統屬性所指定的目錄中加載類庫,或從JDK的安裝目錄的jre/lib/ext子目錄(擴展目錄)下加載類庫。如果用戶創建的JAR放在此目錄下,也會自動由擴展類加載器加載

  3.系統類加載器

應用程序類加載器(也稱為系統類加載器,AppClassLoader)

a.Java語言編寫,由sun.misc.LaunchersAppClassLoader實現

b.派生於ClassLoader類

c.父類加載器為擴展類加載器

d.它負責加載環境變量classpath或系統屬性java.class.path指定路徑下的類庫

e.該類加載是程序中默認的類加載器,一般來說,Java應用的類都是由它來完成加載

f.通過classLoader.getSystemclassLoader()方法可以獲取到該類加載器

public class ClassLoaderTest1 {
    public static void main(String[] args) {
        System.out.println("**********啟動類加載器**************");
        //獲取BootstrapClassLoader能夠加載的api的路徑
        URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        for (URL element : urLs) {
            System.out.println(element.toExternalForm());
        }
        //從上面的路徑中隨意選擇一個類,來看看他的類加載器是什麼:引導類加載器
        ClassLoader classLoader = Provider.class.getClassLoader();
        System.out.println(classLoader);

        System.out.println("***********擴展類加載器*************");
        String extDirs = System.getProperty("java.ext.dirs");
        for (String path : extDirs.split(";")) {
            System.out.println(path);
        }

        //從上面的路徑中隨意選擇一個類,來看看他的類加載器是什麼:擴展類加載器
        ClassLoader classLoader1 = CurveDB.class.getClassLoader();
        System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@1540e19d

    }
}

十、用戶自定義類加載器

  1.什麼時候需要自定義類加載器?

a.隔離加載類(比如說我假設現在Spring框架,和RocketMQ有包名路徑完全一樣的類,類名也一樣,這個時候類就衝突了。不過一般的主流框架和中間件都會 自定義類加載器,實現不同的框架,中間價之間是隔離的)

b.修改類加載的方式

c.擴展加載源(還可以考慮從數據庫中加載類,路由器等等不同的地方)

d.防止源碼泄漏(對位元組碼文件進行解密,自己用的時候通過自定義類加載器來對其進行解密)

  2.如何自定義類加載器?

a.開發人員可以通過繼承抽象類java.lang.ClassLoader類的方式,實現自己的類加載器,以滿足一些特殊的需求

b.在JDK1.2之前,在自定義類加載器時,總會去繼承ClassLoader類並重寫loadClass()方法,從而實現自定義的類加載類,但是在JDK1.2之後已不再建議用戶去覆蓋loadClass()方法,而是建議把自定義的類加載邏輯寫在findclass()方法中

c.在編寫自定義類加載器時,如果沒有太過於複雜的需求,可以直接繼承URIClassLoader類,這樣就可以避免自己去編寫findclass()方法及其獲取位元組碼流的方式,使自定義類加載器編寫更加簡潔。

public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        try {
            byte[] result = getClassFromCustomPath(name);
            if (result == null) {
                throw new FileNotFoundException();
            } else {
                //defineClass和findClass搭配使用
                return defineClass(name, result, 0, result.length);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        throw new ClassNotFoundException(name);
    }
	//自定義流的獲取方式
    private byte[] getClassFromCustomPath(String name) {
        //從自定義路徑中加載指定類:細節略
        //如果指定路徑的位元組碼文件進行了加密,則需要在此方法中進行解密操作。
        return null;
    }

    public static void main(String[] args) {
        CustomClassLoader customClassLoader = new CustomClassLoader();
        try {
            Class<?> clazz = Class.forName("One", true, customClassLoader);
            Object obj = clazz.newInstance();
            System.out.println(obj.getClass().getClassLoader());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

十一、關於ClassLoader

1.ClassLoader 類介紹

         ClassLoader類,它是一個抽象類,其後所有的類加載器都繼承自ClassLoader(不包括啟動類加載器)

    sun.misc.Launcher 它是一個java虛擬機的入口應用

  2.獲取ClassLoader途徑

 

十二、雙親委派機制

  1.雙親委派機制原理

    Java虛擬機對class文件採用的是按需加載的方式,也就是說當需要使用該類時才會將它的class文件加載到內存生成class對象。而且加載某個類的class文件時,Java虛擬機採用的是雙親委派模式,即把請求交由父類處理,它是一種任務委派模式

a.如果一個類加載器收到了類加載請求,它並不會自己先去加載,而是把這個請求委託給父類的加載器去執行;

b.如果父類加載器還存在其父類加載器,則進一步向上委託,依次遞歸,請求最終將到達頂層的啟動類加載器;

c.如果父類加載器可以完成類加載任務,就成功返回,倘若父類加載器無法完成此加載任務,子加載器才會嘗試自己去加載,這就是雙親委派模式。

d.父類加載器一層一層往下分配任務,如果子類加載器能加載,則加載此類,如果將加載任務分配至系統類加載器也無法加載此類,則拋出異常

  2.雙親委派機制優勢

    通過上面的例子,我們可以知道,雙親機制可以

    • 避免類的重複加載

    • 保護程序安全,防止核心API被隨意篡改

      • 自定義類:自定義java.lang.String 沒有被加載。
      • 自定義類:java.lang.ShkStart(報錯:阻止創建 java.lang開頭的類)

十三、沙箱安全機制

  1. 自定義String類時:在加載自定義String類的時候會率先使用引導類加載器加載,而引導類加載器在加載的過程中會先加載jdk自帶的文件(rt.jar包中java.lang.String.class),報錯信息說沒有main方法,就是因為加載的是rt.jar包中的String類。
  2. 這樣可以保證對java核心源代碼的保護,這就是沙箱安全機制。

十四、其他

  1.   如何判斷兩個class對象是否相同?

    在JVM中表示兩個class對象是否為同一個類存在兩個必要條件:

    1. 類的完整類名必須一致,包括包名
    2. 加載這個類的ClassLoader(指ClassLoader實例對象)必須相同
    3. 換句話說,在JVM中,即使這兩個類對象(class對象)來源同一個Class文件,被同一個虛擬機所加載,但只要加載它們的ClassLoader實例對象不同,那麼這兩個類對象也是不相等的

   2.對類加載器的引用

  1. JVM必須知道一個類型是由啟動加載器加載的還是由用戶類加載器加載的
  2. 如果一個類型是由用戶類加載器加載的,那麼JVM會將這個類加載器的一個引用作為類型信息的一部分保存在方法區中
  3. 當解析一個類型到另一個類型的引用的時候,JVM需要保證這兩個類型的類加載器是相同的

Tags: