ClassLoader總結(jié)

通讀這篇文章你會(huì)知道如何回答以下問(wèn)題:

  • Java自帶的三大加載器加載的jar位置都是在哪里?
  • 三大加載器之間的關(guān)系是怎么樣的? 在代碼中是如何體現(xiàn)的?
  • 雙親委派模型是什? 代碼中如何體現(xiàn)這種模式的應(yīng)用? 這種模式的不足是什么?
  • 上下文加載器存在的作用是什么?應(yīng)用的場(chǎng)景有哪些?

java三大加載器加載的jar位置

知道每個(gè)加載器加載什么位置的jar,這對(duì)后面分析委托機(jī)制會(huì)起到作用。
Java語(yǔ)言自帶的有三個(gè)類加載器

  1. Bootstrap CLassloder

  2. Extention ClassLoader

  3. AppClassLoader
    以下是輸出各個(gè)加載器的加載jar的位置,這些路徑可以通過(guò)虛擬機(jī)參數(shù)進(jìn)行修改。

    public static void testClassLoader() {
     System.out.println("BootstrapClassLoader:");
     String property = System.getProperty("sun.boot.class.path");
     Arrays.stream(property.split(";")).forEach(System.out::println);
    
     System.out.println("ExtClassLoader :");
     property = System.getProperty("java.ext.dirs");
     Arrays.stream(property.split(";")).forEach(System.out::println);
    
     System.out.println("AppClassLoader :");
     property = System.getProperty("java.class.path");
     Arrays.stream(property.split(";")).forEach(System.out::println);    }
    
  • Bootstrap CLassloder 加載位置如下:
    JDK\jdk1.8\jre\lib\resources.jar
    JDK\jdk1.8\jre\lib\rt.jar
    JDK\jdk1.8\jre\lib\sunrsasign.jar
    JDK\jdk1.8\jre\lib\jsse.jar
    JDK\jdk1.8\jre\lib\jce.jar
    JDK\jdk1.8\jre\lib\charsets.jar
    JDK\jdk1.8\jre\lib\jfr.jar
    JDK\jdk1.8\jre\classes
  • Extention ClassLoader加載位置如下:
    C:\Program Files\Java\jre1.8.0_91\lib\ext;C:\Windows\Sun\Java\lib\ext;
  • AppClassLoader 加載位置如下:
    就是你項(xiàng)目配置的jar路徑以及工程生成的classes的位置,如maven會(huì)是 target\classes,或者bin目錄;
  • 這些路徑的可以從sun.misc.Launcher類得知;

加載器之間的關(guān)系

  • 如果類是在boostrapClassLoder下加載,無(wú)法獲取其加載器
    從報(bào)錯(cuò)的情況,因?yàn)閎ootstrapclassloader有加載過(guò)rt.jar,這個(gè)從前面可以看出來(lái),Integer類是rt.jar里面的類,所以Integer是由bootstrapclassloader進(jìn)行加載的沒(méi)錯(cuò),那為啥是空指針呢?

    1-1

    來(lái)一張extclassloader的繼承圖,AppClassLoader也一樣的繼承關(guān)系
    圖1

    這個(gè)空指針我是這么理解的:凡是有boostraploader進(jìn)行加載的類,都是獲取不到此類的加載器,因?yàn)锽ootstrap ClassLoader是由C/C++編寫(xiě)的,它本身是虛擬機(jī)的一部分,所以它并不是一個(gè)JAVA類,也就是無(wú)法在java代碼中獲取它的引用;

  • ExtClassLoadder的父加載器是null
    接下來(lái)從源碼角度來(lái)看下,圖2輸出的原因(即extClassLoader的父記載器為啥null)

    圖2

    關(guān)鍵從parent方法入手,可以發(fā)現(xiàn)在圖一的繼承關(guān)系中找到parent方法是在ClassLoader中如圖3,從idea點(diǎn)擊過(guò)去也可以找到,從方法來(lái)看parent是final成員變量,所以找到構(gòu)造方法,就知道如何它是如何初始化了
    圖3

    從下面的代碼可以看出parent初始化有兩種方式:
    一種是指定ClassLoader;
    第二種如果沒(méi)有指定則通過(guò)initSystemClassLoader方法進(jìn)行初始化,這個(gè)方法獲取classLoader方式是從Launcher類的getClassLoader方法獲取的;

     protected ClassLoader(ClassLoader parent) {
     this(checkCreateClassLoader(), parent);
     }   
      protected ClassLoader() {
     this(checkCreateClassLoader(), getSystemClassLoader());
     }
     public static ClassLoader getSystemClassLoader() {
     initSystemClassLoader();
     if (scl == null) {
         return null;
     }
     SecurityManager sm = System.getSecurityManager();
     if (sm != null) {
         checkClassLoaderPermission(scl, Reflection.getCallerClass());
     }
            return scl;
     }
     private static synchronized void initSystemClassLoader() {
     if (!sclSet) {
         if (scl != null)
             throw new IllegalStateException("recursive invocation");
         sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
         if (l != null) {
             Throwable oops = null;
             scl = l.getClassLoader();
             try {
                 scl = AccessController.doPrivileged(
                     new SystemClassLoaderAction(scl));
             } catch (PrivilegedActionException pae) {
                 oops = pae.getCause();
                 if (oops instanceof InvocationTargetException) {
                     oops = oops.getCause();
                 }
             }
             if (oops != null) {
                 if (oops instanceof Error) {
                     throw (Error) oops;
                 } else {
                     // wrap the exception
                     throw new Error(oops);
                 }
             }
         }
         sclSet = true;
     }   
      }
    

Launcher類的classLoader方法初始化如下:
從代碼可知Launcher.getClassLoader就是獲取appclassLoader,意味著一個(gè)類的父加載器如果沒(méi)有指定,則是默認(rèn)就是AppClassLoader。

    public Launcher() {
     Launcher.ExtClassLoader var1;
     try {
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
     } catch (IOException var10) {
        throw new InternalError("Could not create extension class loader", var10);
    }
 try {
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }

    Thread.currentThread().setContextClassLoader(this.loader);
    String var2 = System.getProperty("java.security.manager");
    if(var2 != null) {
        SecurityManager var3 = null;
        if(!"".equals(var2) && !"default".equals(var2)) {
            try {
                var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
            } catch (IllegalAccessException var5) {
                ;
            } catch (InstantiationException var6) {
                ;
            } catch (ClassNotFoundException var7) {
                ;
            } catch (ClassCastException var8) {
                ;
            }
        } else {
            var3 = new SecurityManager();
        }
        if(var3 == null) {
            throw new InternalError("Could not create SecurityManager: " + var2);
        }
        System.setSecurityManager(var3);
    }
}
public ClassLoader getClassLoader() {
    return this.loader;
}

但是這個(gè)還是無(wú)法解釋extClassLoader的父加載器是null,請(qǐng)看Laucher類中ExtClassLoader類的構(gòu)造法方方法,指定的父加載器就是null,所以從parent變量的獲取追蹤到extClassloader是由于指定null父加載器,所以導(dǎo)致extClassLoader獲取父加載器是null,
而AppClassLoader獲取父加載器是extClassLoader,是因?yàn)橹付薳xtClassLoader為自己的父加載器;

extclassLoader構(gòu)造方法

appclassLoader構(gòu)造方法

  • 從源碼角度理解雙親委托機(jī)制以及boostrapClassLoader為啥可以作為extClassLoader的父加載器
    雙親委托是什么,看過(guò)博主的文章就知道了,一句話概括

委托是從下向上,然后具體查找過(guò)程卻是自上至下

從ClassLoader的loadClass可以明白雙親委托機(jī)制過(guò)程,同時(shí)知道如果自定義的ClassLoader是覆蓋findClass,而不是loadClass,采用這種方式進(jìn)行加載可以避免java核心api中定義的類型被自定義的加載器加載,從而出現(xiàn)多個(gè)


ClassLoadere-loadClass
  • 自定義加載器,自己也寫(xiě)個(gè),跟博主一樣,然后調(diào)試了一遍,對(duì)加載過(guò)程再熟悉一遍

    public class MyClassLoader extends  ClassLoader{
    private String filePath;
    
    MyClassLoader(String filePath) {
      this.filePath = filePath;
    }
    
    @Override
     public Class<?> findClass(String name) throws ClassNotFoundException {
      int i = name.lastIndexOf(".") +1;
      String s = name.substring(i) + ".class";
      File file = new File(filePath, s);
      try {
          FileInputStream is = new FileInputStream(file);
          ByteArrayOutputStream bos = new ByteArrayOutputStream();
          int len = 0;
          try {
               while ((len = is.read()) != -1) {
                  bos.write(len);
              }
          } catch (IOException e) {
              e.printStackTrace();
          }
          byte[] data = bos.toByteArray();
          is.close();
          bos.close();
          return defineClass(name,data,0,data.length);
      } catch (IOException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
      }
      return super.findClass(name);
    }
    
    public static void main(String[] args) throws Exception{
      MyClassLoader classLoader = new MyClassLoader("D:\\");
      Class<?> aClass = classLoader.loadClass("com.example.Product");
      if (aClass != null) {
          Object o = aClass.newInstance();
          Method ok = aClass.getDeclaredMethod("ok");
          ok.invoke(o);
      }
    }
    }
    
  • 打破雙親委派模型------引出上下文類加載器
    雙親委派模型的加載方式是從底層加載器到頂層加載器進(jìn)行加載,但是如果頂層加載器要加載來(lái)自底層加載器的類時(shí),此時(shí)就傳統(tǒng)加載模式就無(wú)法完成了。
    這種情況會(huì)出現(xiàn)在SPI使用中,引用一段話來(lái)說(shuō)明SPI加載的情況

SPI機(jī)制簡(jiǎn)介
SPI的全名為Service Provider Interface,主要是應(yīng)用于廠商自定義組件或插件中。在java.util.ServiceLoader的文檔里有比較詳細(xì)的介紹。簡(jiǎn)單的總結(jié)下java SPI機(jī)制的思想:我們系統(tǒng)里抽象的各個(gè)模塊,往往有很多不同的實(shí)現(xiàn)方案,比如日志模塊、xml解析模塊、jdbc模塊等方案。面向的對(duì)象的設(shè)計(jì)里,我們一般推薦模塊之間基于接口編程,模塊之間不對(duì)實(shí)現(xiàn)類進(jìn)行硬編碼。一旦代碼里涉及具體的實(shí)現(xiàn)類,就違反了可拔插的原則,如果需要替換一種實(shí)現(xiàn),就需要修改代碼。為了實(shí)現(xiàn)在模塊裝配的時(shí)候能不在程序里動(dòng)態(tài)指明,這就需要一種服務(wù)發(fā)現(xiàn)機(jī)制。 Java SPI就是提供這樣的一個(gè)機(jī)制:為某個(gè)接口尋找服務(wù)實(shí)現(xiàn)的機(jī)制。有點(diǎn)類似IOC的思想,就是將裝配的控制權(quán)移到程序之外,在模塊化設(shè)計(jì)中這個(gè)機(jī)制尤其重要。
SPI具體約定
Java SPI的具體約定為:當(dāng)服務(wù)的提供者提供了服務(wù)接口的一種實(shí)現(xiàn)之后,在jar包的META-INF/services/目錄里同時(shí)創(chuàng)建一個(gè)以服務(wù)接口命名的文件。該文件里就是實(shí)現(xiàn)該服務(wù)接口的具體實(shí)現(xiàn)類。而當(dāng)外部程序裝配這個(gè)模塊的時(shí)候,就能通過(guò)該jar包META-INF/services/里的配置文件找到具體的實(shí)現(xiàn)類名,并裝載實(shí)例化,完成模塊的注入?;谶@樣一個(gè)約定就能很好的找到服務(wù)接口的實(shí)現(xiàn)類,而不需要再代碼里制定。jdk提供服務(wù)實(shí)現(xiàn)查找的一個(gè)工具類:java.util.ServiceLoader。

以下面這段代碼來(lái)說(shuō)明雙親委派模型的 逆向加載

    String url = "jdbc:mysql://localhost:3306/mysql";
      //通過(guò)java庫(kù)獲取數(shù)據(jù)庫(kù)連接
    Connection conn = java.sql.DriverManager.getConnection(url, "root", "123456");

首先調(diào)用靜態(tài)方法必將導(dǎo)致調(diào)用者會(huì)進(jìn)行初始化,所以DriverMananger類將初始化,如果類中有靜態(tài)代碼塊必將執(zhí)行,下面是DriverManager類的代碼:


靜態(tài)代碼k
loadInitialDrivers方法

class.forName()加載用的是調(diào)用者的Classloader,這個(gè)調(diào)用者DriverManager是在rt.jar中的,ClassLoader是啟動(dòng)類加載器,而com.mysql.jdbc.Driver肯定不在<JAVA_HOME>/lib下,所以肯定是無(wú)法加載mysql中的這個(gè)類的。這就是雙親委派模型的局限性了,父級(jí)加載器無(wú)法加載子級(jí)類加載器路徑中的類
此時(shí)如何抉擇呢,按照目前情況來(lái)分析,這個(gè)mysql的drvier只有應(yīng)用類加載器能加載,那么我們只要在啟動(dòng)類加載器中有方法獲取應(yīng)用程序類加載器,然后通過(guò)它去加載就可以了。這就是所謂的線程上下文加載器
這loader如何獲取的,可以從load方法中看出:

load

所以這邊就可以看出要通過(guò)頂層加載器去加載底層加載器的類時(shí),通過(guò)上下文加載器實(shí)現(xiàn);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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