ClassLoader和雙親委派機制
博文主要講classloader的模型、作用和使用,內容是作者學習java反射機制有關知識時記錄的筆記。
ClassLoader
ClassLoad:類載入器(class loader)用來載入 Java 類到 Java 虛擬機器中。Java 源程式(.java 檔案)在經過 Java 編譯器編譯之後就被轉換成 Java 位元組程式碼(.class 檔案)。類載入器負責讀取 Java 位元組程式碼,並轉換成 java.lang.Class 類的一個例項。
雙親委派機制
某個特定的類載入器在接到載入類的請求時,首先將載入任務委託給父類載入器,依次遞迴,如果父類載入器可以完成類載入任務,就成功返回;只有父類載入器無法完成此載入任務時,才自己去載入。
雙親委派機制的好處
採用雙親委派模式的是好處是Java類隨著它的類載入器一起具備了一種帶有優先順序的層次關係,通過這種層級關可以避免類的重複載入,當父親已經載入了該類時,就沒有必要子ClassLoader再載入一次。其次是考慮到安全因素,java核心api中定義型別不會被隨意替換,假設通過網路傳遞一個名為java.lang.Integer的類,通過雙親委託模式傳遞到啟動類載入器,而啟動類載入器在核心Java API發現這個名字的類,發現該類已被載入,並不會重新載入網路傳遞的過來的java.lang.Integer,而直接返回已載入過的Integer.class,這樣便可以防止核心API庫被隨意篡改。
自己對JVM中的ClassLoader和雙親委派機制的一些理解:
- java虛擬機器中的class其實都是通過classloader來裝載的
- 只有當你使用該class的時候才會去裝載,一個classloader只會裝載同一個class一次。
- 不同的類載入器的例項所載入的位元組碼檔案,其通過反射獲取的物件不是相同型別(相互賦值會丟擲型別強轉異常)。即:判斷兩個類是否為同一物件的標準裡面有一條是類載入器必須為相同。
- 雙親委派機制能在很大程度上防止記憶體中出現多個相同的位元組碼檔案。
- 在載入類的時候預設會使用當前類的ClassLoader進行載入(類A中引用了類B,JVM會用類A的類載入器載入類B)。
- 線上程中載入一個類的時候:當前執行緒的類載入器可以通過Thread類的getContextClassLoader()獲得,也可以通過setContextClassLoader()自己設定類載入器(PS:自己沒有試驗過)。
ClassLoader體系結構圖
package com.tzx.reflection;
public class MyClassLoader {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getContextClassLoader());
System.out.println(ClassLoaderTest.class.getClassLoader());
System.out.println(System.class.getClassLoader());
System.out.println(ClassLoader.getSystemClassLoader());
System.out.println(ClassLoader.getSystemClassLoader().getParent());
System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
執行結果:
sun.misc.Launcher$AppClassLoader@4aa298b7
sun.misc.Launcher$AppClassLoader@4aa298b7
null
sun.misc.Launcher$AppClassLoader@382f3bf0
sun.misc.Launcher$ExtClassLoader@25082661
null
- 1
- 2
- 3
- 4
- 5
- 6
按照雙親委派機制載入類,每當需要載入一個新的類時,當前的類載入器會先委託其父載入器,查詢有沒有載入該類。如果父類載入器已近載入該類,那麼直接返回載入的class物件,如果沒有那麼繼續向上尋找父類載入器,如果在祖宗類載入器Bootstrap都沒有載入該類,那麼需要當前的類載入器自己載入,如果當前的類載入器也不能載入則會跑出ClassNotFoundException
異常 (PS:類載入器沒有向下尋找,沒有getChild只有getParent)。
用這種思想去解析上邊程式碼:Thread.currentThread().getContextClassLoader()指出當前的類載入器是AppClassLoader,需要載入MyClassLoader.class先在父類載入器(ExtClassLoader)中尋找,沒有再向祖宗類載入中尋找(Bootstrap ClassLoader),還沒有找到那麼AppClassLoader自己去載入。
JVM中的類載入器型別:
- (Bootstrap ClassLoader)啟動類載入器:
負責載入java_home/jar/lib/rt.jar目錄下的核心類或- Xbootclasspath指定目錄下的類。由於引導類載入器全部是native程式碼來實現的並且涉及到虛擬機器本地實現細節,開發者無法直接獲取到啟動類載入器的引用,所以不允許直接通過引用進行操作,從上面的ClassLoader體系結構圖中可以看出在java程式碼中獲取啟動類載入器為null。
像System.java這樣的由系統提供的類都在rt.jar中,由Bootstrap ClassLoader載入,由於Bootstrap類載入器不是Java寫的,所以打印出來的類名為null。
- (Extension)擴充套件類載入器:負責載入java_home/lib/ext目錄下的擴充套件類或 -Djava.ext.dirs 指定目錄下的類。 開發者可以直接使用標準擴充套件類載入器。
我們將第一段程式碼生產的MyClassLoader.class檔案打包成jar(java打包成jar|執行jar包中的main方法),放在java_home/jar/lib/ext目錄下。
再次執行該java程式
im@58user:/usr/lib/jvm/jdk1.8.0_101/jre/lib/ext$ java -cp MyClassLoader.jar com.loadclass.demo.ClassLoaderTest start
sun.misc.Launcher$AppClassLoader@55f96302
sun.misc.Launcher$ExtClassLoader@70dea4e
null
sun.misc.Launcher$AppClassLoader@55f96302
sun.misc.Launcher$ExtClassLoader@70dea4e
null
- 1
- 2
- 3
- 4
- 5
- 6
- 7
通過終端輸出結果我們可以看到執行ClassLoaderTest程式的類載入器是AppClassLoader,但載入ClassLoaderTest類的類載入器是ExtClassLoader。因為java_home/jar/lib/ext/.jar在執行程式之前就被ExtClassLoader類載入器載入過了。這樣避免了類的重複載入~!~!*
- (System)類載入器:負責載入-classpath/-Djava.class.path所指的目錄下的類。開發者可以直接使用標準擴充套件類載入器。一般來說,Java應用的類都是由他來完成載入的。
除了以上三種類型的類載入器,還有一箇中比較特殊的:執行緒上下文類載入器(PS:暫時沒做這方面的記錄)。
自定義類載入器
- 定義
package com.tzx.reflection;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class MyClassLoader extends ClassLoader{
static {
System.out.println("MyClassLoader");
}
public static final String driver = "/home/im/Desktop/";
public static final String fileTyep = ".class";
public Class findClass(String name) {
byte[] data = loadClassData(name);
return defineClass(data, 0, data.length);
}
public byte[] loadClassData(String name) {
FileInputStream fis = null;
byte[] data = null;
try {
File file = new File(driver + name + fileTyep);
System.out.println(file.getAbsolutePath());
fis = new FileInputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int ch = 0;
while ((ch = fis.read()) != -1) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
System.out.println("loadClassData-IOException");
}
return data;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 使用
public class ClassLoaderTest {
public static void main(String[] args) {
MyClassLoader cl1 = new MyClassLoader();
//磁碟中/home/im/Desktop/Hello.class檔案存在
try {
Class c1 = cl1.loadClass("Hello");
Object object = c1.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
System.out.println("main-ClassNotFoundException");
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
破壞雙親委派
因為在某些情況下父類載入器需要委託子類載入器去載入class檔案。受到載入範圍的限制,父類載入器無法載入到需要的檔案,以Driver介面為例,由於Driver介面定義在jdk當中的,而其實現由各個資料庫的服務商來提供,比如mysql的就寫了 MySQL Connector ,那麼問題就來了,DriverManager(也由jdk提供)要載入各個實現了Driver介面的實現類,然後進行管理,但是DriverManager由啟動類載入器載入,只能記載JAVA_HOME的lib下檔案,而其實現是由服務商提供的,由系統類載入器載入,這個時候就需要啟動類載入器來委託子類來載入Driver實現,從而破壞了雙親委派,這裡僅僅是舉了破壞雙親委派的其中一個情況。
博文主要講classloader的模型、作用和使用,內容是作者學習java反射機制有關知識時記錄的筆記。
ClassLoader
ClassLoad:類載入器(class loader)用來載入 Java 類到 Java 虛擬機器中。Java 源程式(.java 檔案)在經過 Java 編譯器編譯之後就被轉換成 Java 位元組程式碼(.class 檔案)。類載入器負責讀取 Java 位元組程式碼,並轉換成 java.lang.Class 類的一個例項。
雙親委派機制
某個特定的類載入器在接到載入類的請求時,首先將載入任務委託給父類載入器,依次遞迴,如果父類載入器可以完成類載入任務,就成功返回;只有父類載入器無法完成此載入任務時,才自己去載入。