整理Java中的ClassLoader核心知識點
本文整理了基于 JDK8 的 ClassLoader 核心知識點,包括 JVM 中 ClassLoader 種類、ClassLoader 執(zhí)行順序、父加載器概念、雙親委派機制、自定義類加載器。
JDK 和 JRE 的作用
- JDK 提供了 java 的編程環(huán)境,它包含編譯調(diào)試的環(huán)境功能,包含 JRE(JDK 目錄中的 JRE 為專用 JRE,而安裝后與 JDK 同目錄的 JRE 為公用 JRE)。開發(fā)時一般運行的是 JDK 專用JRE,而運行外部程序時一般運行的是公用 JRE,實現(xiàn)了分工不同的 jre 負責各自范圍的內(nèi)容。
 - JRE 提供了 JAVA 程序運行的必要環(huán)境平臺
 
JAVAHOME、PATH、CLASSPATH
- JAVAHOME: JDK安裝的位置路徑,如:D:\Program Files\Java\jdk1.8.0_241
 - PATH: 配置后運行 bin 中的命令不需要補全全路徑,如可以在任意的位置運行 java 和 javac 命令, %JAVA_HOME%\bin;
 - CLASSPATH:指向jar包路徑 %JAVA_HOME%\lib;
 
類加載器的種類
在JVM中有三類ClassLoader構(gòu)成:
- Bootstrap ClassLoader 啟動類(或根類)加載器
 - Extention ClassLoader 擴展的類加載器
 - Appclass Loader 應(yīng)用類加載器
 

(1) Bootstrap ClassLoader
Bootstrap ClassLoader 最頂層的類加載器,主要加載核心類庫 %JRE_HOME%\lib 下的 rt.jar、resources.jar、charsets.jar 和 class文件等。
- //執(zhí)行
 - System.out.println(System.getProperty("sun.boot.class.path"));
 - //輸出結(jié)果
 - D:\Program Files\Java\jdk1.8.0_241\jre\lib\resources.jar;
 - D:\Program Files\Java\jdk1.8.0_241\jre\lib\rt.jar;
 - D:\Program Files\Java\jdk1.8.0_241\jre\lib\sunrsasign.jar;
 - D:\Program Files\Java\jdk1.8.0_241\jre\lib\jsse.jar;
 - D:\Program Files\Java\jdk1.8.0_241\jre\lib\jce.jar;
 - D:\Program Files\Java\jdk1.8.0_241\jre\lib\charsets.jar;
 - D:\Program Files\Java\jdk1.8.0_241\jre\lib\jfr.jar;
 - D:\Program Files\Java\jdk1.8.0_241\jre\classes
 
(2) Extention ClassLoader
Extention ClassLoader 擴展的類加載器,主要加載目錄 %JRE_HOME%\lib\ext 目錄下的jar包和class文件。
- //執(zhí)行
 - System.out.println(System.getProperty("java.ext.dirs"));
 - //輸出
 - D:\Program Files\Java\jdk1.8.0_241\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
 
(3) Appclass Loader
Appclass Loader也稱為SystemAppClass 加載當前應(yīng)用的classpath的所有類。
類加載器的執(zhí)行順序
除啟動類加載器(Bootstrap ClassLoader)外,擴展類加載器和應(yīng)用類加載器都是通過類sun.misc.Launcher進行初始化,而Launcher類則由啟動類加載器進行加載。Launcher相關(guān)代碼如下:
- public Launcher() {
 - Launcher.ExtClassLoader var1;
 - try {
 - //初始化擴展類加載器,構(gòu)造函數(shù)沒有入?yún)?,無法獲取啟動類加載器
 - var1 = Launcher.ExtClassLoader.getExtClassLoader();
 - } catch (IOException var10) {
 - throw new InternalError("Could not create extension class loader", var10);
 - }
 - try {
 - //初始化應(yīng)用類加載器,入?yún)閿U展類加載器
 - this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
 - } catch (IOException var9) {
 - throw new InternalError("Could not create application class loader", var9);
 - }
 - // 設(shè)置上下文類加載器
 - Thread.currentThread().setContextClassLoader(this.loader);
 - //...
 - }
 
加載順序:Bootstrap CLassloder > Extention ClassLoader > AppClassLoader
父加載器概念
AppClassLoader 和 ExtClassLoader 都繼承了 URLClassLoader。每個類加載器都有一個父加載器(注意:父類和父加載器是兩個不同的概念),可通過 getParent() 獲取父類加載器。
- System.out.println("ClassLoader is:"+cl.toString());
 - System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString());System.out.println("ClassLoader\'s grand father is:"+cl.getParent().getParent().toString());
 
- AppClassLoader 的父加載器是ExtClassLoader
 - ExtClassLoader的父加載器是Bootstrap ClassLoader(上面代碼輸出 ExtClassLoader 為null 是因為 Bootstrap ClassLoader 本身不是一個Java 類所致)
 - Bootstrap ClassLoader是由C/C++編寫的,它本身是虛擬機的一部分,所以它并不是一個JAVA類,也就是無法在java代碼中獲取它的引用,JVM啟動時通過Bootstrap類加載器加載rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加載。然后呢,我們前面已經(jīng)分析了,JVM初始化sun.misc.Launcher并創(chuàng)建Extension ClassLoader和AppClassLoader實例。并將ExtClassLoader設(shè)置為AppClassLoader的父加載器。Bootstrap沒有父加載器,但是它卻可以作用一個ClassLoader的父加載器。
 
雙親委派
雙親委派模型:當一個類加載器接收到類加載請求時,會先請求其父類加載器加載,依次遞歸,當父類加載器無法找到該類時(根據(jù)類的全限定名稱),子類加載器才會嘗試去加載。


時序圖
為什么使用雙親委派模型?
雙親委派模型是為了保證Java核心庫的類型安全。所有Java應(yīng)用都至少需要引用java.lang.Object類,在運行時這個類需要被加載到Java虛擬機中。如果該加載過程由自定義類加載器來完成,可能就會存在多個版本的java.lang.Object類,而且這些類之間是不兼容的。
通過雙親委派模型,對于Java核心庫的類的加載工作由啟動類加載器來統(tǒng)一完成,保證了Java應(yīng)用所使用的都是同一個版本的Java核心庫的類,是互相兼容的。
自定義類加載器
不管是Bootstrap ClassLoader還是ExtClassLoader等,這些類加載器都只是加載指定的目錄下的jar包或者資源。如果我們需要動態(tài)加載比如從指定目錄中加載一個class文件,這時候通過自定義類加載器可以實現(xiàn)。
自定義類加載器只需要繼承java.lang.ClassLoader類,然后重寫findClass(String name)方法即可,在方法中指明如何獲取類的字節(jié)碼流。如果要破壞雙親委派規(guī)范的話,還需重寫loadClass方法(雙親委派的具體邏輯實現(xiàn))。但不建議這么做。
- public class ClassLoaderTest extends ClassLoader {
 - private String classPath;
 - public ClassLoaderTest(String classPath) {
 - this.classPath = classPath;
 - }
 - /**
 - * 編寫findClass方法的邏輯
 - *
 - * @param name
 - * @return
 - * @throws ClassNotFoundException
 - */
 - @Override
 - protected Class<?> findClass(String name) throws ClassNotFoundException {
 - // 獲取類的class文件字節(jié)數(shù)組
 - byte[] classData = getClassData(name);
 - if (classData == null) {
 - throw new ClassNotFoundException();
 - } else {
 - // 生成class對象
 - return defineClass(name, classData, 0, classData.length);
 - }
 - }
 - /**
 - * 編寫獲取class文件并轉(zhuǎn)換為字節(jié)碼流的邏輯
 - *
 - * @param className
 - * @return
 - */
 - private byte[] getClassData(String className) {
 - // 讀取類文件的字節(jié)
 - String path = classNameToPath(className);
 - try {
 - InputStream is = new FileInputStream(path);
 - ByteArrayOutputStream stream = new ByteArrayOutputStream();
 - byte[] buffer = new byte[2048];
 - int num = 0;
 - // 讀取類文件的字節(jié)碼
 - while ((num = is.read(buffer)) != -1) {
 - stream.write(buffer, 0, num);
 - }
 - return stream.toByteArray();
 - } catch (IOException e) {
 - e.printStackTrace();
 - }
 - return null;
 - }
 - /**
 - * 類文件的完全路徑
 - *
 - * @param className
 - * @return
 - */
 - private String classNameToPath(String className) {
 - return classPath + File.separatorChar
 - + className.replace('.', File.separatorChar) + ".class";
 - }
 - public static void main(String[] args) {
 - String classPath = "/Users/zzs/my/article/projects/java-stream/src/main/java/";
 - ClassLoaderTest loader = new ClassLoaderTest(classPath);
 - try {
 - //加載指定的class文件
 - Class<?> object1 = loader.loadClass("com.secbro2.classload.SubClass");
 - System.out.println(object1.newInstance().toString());
 - } catch (Exception e) {
 - e.printStackTrace();
 - }
 - }
 - }
 















 
 
 















 
 
 
 