十分鐘快速理解ClassLoader

JVM

概述

類的加載機制是JVM的重要部分,而ClassLoader在其中扮演重要角色,話不多說十分鐘帶你快速理解ClassLoader的工作機制


深入ClassLoader

1.ClassLoader定義和ClassLoader類型

ClassLoader的作用就是根據(jù)一個指定的類的全限定名,找到對應(yīng)的Class字節(jié)碼文件,然后加載它轉(zhuǎn)化成一個java.lang.Class類的一個實例。Java默認提供三個ClassLoader:

  • Bootstrap ClassLoader(啟動類加載器)
    負責(zé)加載Java安裝目錄下的/jre/lib類庫(核心類庫)至JVM中,不繼承java.lang.ClassLoader,不可以被Java程序直接調(diào)用,本身是用C++寫的
  • Extendsion ClassLoader(擴展類加載器)
    負責(zé)加載Java安裝目錄下的/jre/lib/ext類庫(擴展類庫)至JVM,Java程序可直接調(diào)用
  • Application ClassLoader(應(yīng)用程序類加載器)
    負責(zé)加載CLASSPATH路徑下的類庫,我們寫的類就是通過這個加載器完成加載,可以通過 ClassLoader.getSystemClassLoader()來獲取這個加載器

除了以上三個加載器外,還可以自定義加載器,各個加載器之間的層級關(guān)系(不是繼承,是一個父加載器引用,有興趣可以看ClassLoader源碼,定義了一個parent的屬性)如下圖,系統(tǒng)類加載器的父類加載器是擴展類加載器,擴展類加載器的父類加載器是啟動類加載器;除此之外自定義加載器的父類加載器是加載此自定義加載器的加載器(暈不?),我們自定義加載器都是放在CLASSPATH路徑下的一個類,所以我們自定義加載器一般都是由應(yīng)用程序類加載器來完成加載的,所以一般自定義加載器的父類加載器就是應(yīng)用程序類加載器,也就有下圖里面的關(guān)系


加載器關(guān)系

可以執(zhí)行下面這段代碼,看看加載器之間的層級關(guān)系

public class Test {
    public static void main(String[] args) {
        ClassLoader loader = Test.class.getClassLoader();

        System.out.println(loader.toString());

        System.out.println(loader.getParent().toString());

        System.out.println(loader.getParent().getParent());

    }
}

結(jié)果:

  • sun.misc.Launcher$AppClassLoader@18b4aac2
  • sun.misc.Launcher$ExtClassLoader@568db2f2
  • null

可以看出加載Test的Class實例的ClassLoader是AppClassLoader,AppClassLoader的父加載器是ExtClassLoader,最后打印出null是因為Bootstrap ClassLoader是C++編寫,是無法直接通過Java代碼獲取到的

2.雙親委派機制

ClassLoaer通過雙親委派機制來加載類,根據(jù)上圖我們知道每個加載器都有一個父加載器(除了啟動類加載器)。當(dāng)一個ClassLoader實例需要加載某個類時,當(dāng)前ClassLoader實例會判斷Class實例是否已經(jīng)加載,已經(jīng)加載了就直接返回Class實例,如果沒有加載則委派給他的父加載器,從下至上檢查直到委派給啟動類加載器;到達啟動類加載器后,由啟動類加載器先嘗試加載Class實例,加載成功則返回Class實例,如果沒有加載成功則把任務(wù)交給擴展類加載器,從上至下直到任務(wù)返回給最初的委托者,最后還沒加載成功的話就拋出ClassNotFoundException異常


雙親委派機制

3.雙親委派機制的好處

雙親委派機制避免了重復(fù)加載Class實例的情況,當(dāng)父加載器已經(jīng)加載時,不會再次加載,可以減少JVM的內(nèi)存開銷

4.類的加載方式、loadClass()與forName()區(qū)別

類加載方式
  • 隱式加載:通過new調(diào)用構(gòu)造方法實例化對象時,隱式調(diào)用類加載器加載對應(yīng)的Class到JVM中
  • 顯示加載:通過loadClass(),forName()方法顯示加載Class,通過調(diào)用newInstance()方法得到具體對象實例,無法調(diào)用帶參數(shù)的構(gòu)造器
ClassLoader的loadClass()與Class的forName()區(qū)別

先看看類裝載的整個流程:


類裝載流程

整體分為加載、鏈接、初始化三大步,二者之間的區(qū)別就在于forName()三大步都執(zhí)行了,loadClass()只執(zhí)行了第一步,可執(zhí)行以下代碼驗證forName()完成了初始化而loadClass()未進行初始化。
先一個實體類:

public class Person {
    private String name;
    private void sayHi(String content){
        System.out.println("hello " + content);
    }
    public void welcome(){
        System.out.println("welcome "+name);
    }
    static {
        System.out.println("init");
    }
}

測試1:

public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader loader = Person.class.getClassLoader();
        Class pc = loader.loadClass("com.example.springboot.Entity.Person");

//        Class pc = Class.forName("com.example.springboot.Entity.Person");

    }
}
執(zhí)行結(jié)果1

測試2:

public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
//        ClassLoader loader = Person.class.getClassLoader();
//        Class pc = loader.loadClass("com.example.springboot.Entity.Person");
        Class pc = Class.forName("com.example.springboot.Entity.Person");

    }
}

執(zhí)行結(jié)果:


執(zhí)行結(jié)果2

從兩個測試的結(jié)果來看,顯然forName()完成了初始化而loadClass()未進行初始化


總結(jié)

本文主要闡明以下幾點:

  • ClassLoader定義和ClassLoader類型
  • 雙親委派機制
  • 雙親委派機制的好處
  • 類的加載方式、loadClass()與forName()區(qū)別
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容