類(lèi)加載器(ClassLoader)是負(fù)責(zé)讀取 Java 字節(jié)碼,并轉(zhuǎn)換成 java.lang.Class 類(lèi)的一個(gè)實(shí)例的代碼模塊。
類(lèi)加載器除了用于加載類(lèi)外,還可用于確定類(lèi)在Java虛擬機(jī)中的唯一性。在整個(gè)JVM里,縱然全限定名相同,若類(lèi)加載器不同,則仍然不算作是同一個(gè)類(lèi),無(wú)法通過(guò) instanceOf 、equals 等方式的校驗(yàn)。
1. 類(lèi)加載器
類(lèi)加載器層級(jí)關(guān)系如下圖所示, Bootstrap、Extension、Application 三者并非是繼承關(guān)系,而是子類(lèi)加載器指派父類(lèi)加載器為自己的 parent屬性。由于啟動(dòng)類(lèi)加載器是內(nèi)嵌于 JVM 且無(wú)法被引用,因此 Extension Classloader 設(shè)置null為parent,即等同于指派啟動(dòng)類(lèi)加載器為自己的父加載器。

1.1 Bootstrap ClassLoader
由C++語(yǔ)言實(shí)現(xiàn)的,并不是繼承自java.lang.ClassLoader,沒(méi)有父類(lèi)加載器。
加載Java核心類(lèi)庫(kù),如:$JAVA_HOME/jre/lib/rt.jar、resources.jar、sun.boot.class.path路徑下的包,用于提供JVM運(yùn)行所需的包。
它加載擴(kuò)展類(lèi)加載器和應(yīng)用程序類(lèi)加載器,并成為他們的父類(lèi)加載器。
1.2 Extension ClassLoader
Java語(yǔ)言編寫(xiě),繼承自java.lang.ClassLoader,父類(lèi)加載器為啟動(dòng)類(lèi)加載器。
負(fù)責(zé)加載java平臺(tái)中擴(kuò)展功能的一些jar包。從系統(tǒng)屬性:java.ext.dirs目錄中加載類(lèi)庫(kù),或者從JDK安裝目。錄:jre/lib/ext目錄下加載類(lèi)庫(kù)。我們可以將我們自己的包放在以上目錄下,就會(huì)自動(dòng)加載進(jìn)來(lái)了。
1.3 Application ClassLoader
Java語(yǔ)言編寫(xiě),繼承自java.lang.ClassLoader,父類(lèi)加載器為啟動(dòng)類(lèi)加載器
它負(fù)責(zé)加載環(huán)境變量classpath或者系統(tǒng)屬性java.class.path指定路徑下的類(lèi)庫(kù)
是程序中默認(rèn)的類(lèi)加載器,我們Java程序中的類(lèi),都是由它加載完成的。
我們可以通過(guò)ClassLoader#getSystemClassLoader()獲取并操作這個(gè)加載器。
1.4 User ClassLoader
Java語(yǔ)言編寫(xiě),根據(jù)自身需要實(shí)現(xiàn)ClassLoader自定義加載class,如tomcat、jboss都會(huì)根據(jù)j2ee規(guī)范自行實(shí)現(xiàn)ClassLoader。通過(guò)靈活定義classloader的加載機(jī)制,我們可以完成很多事情,例如解決類(lèi)沖突問(wèn)題,實(shí)現(xiàn)熱加載以及熱部署,甚至可以實(shí)現(xiàn)jar包的加密保護(hù)。實(shí)現(xiàn)自定義ClassLoader的示例參考章節(jié)3自定義類(lèi)加載器
// App ClassLoader
System.out.println(this.getClass().getClassLoader());
// Ext ClassLoader
System.out.println(this.getClass().getClassLoader().getParent());
// Bootstrap ClassLoader
System.out.println(this.getClass().getClassLoader().getParent().getParent());
// Bootstrap ClassLoader
System.out.println(new String().getClass().getClassLoader());
輸出結(jié)果如下,Bootstrap ClassLoader屬于JVM的范疇,所以是null
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@3ac42916
null
null
3. 加載類(lèi)的方式
1. 隱式加載:
通過(guò)new隱式加載,創(chuàng)建對(duì)象時(shí),如果類(lèi)未加載也會(huì)嘗試加載,例如 Student s = new Student();會(huì)嘗試加載Student類(lèi)
2. 顯示加載:
- 通過(guò)
Class.forName(類(lèi)全限定名)加載
public static Class<?> forName(String className) throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
其中forName0() 方法調(diào)用中的參數(shù) true 表示要初始化該類(lèi)。包括:
- 執(zhí)行靜態(tài)代碼塊
- 初始化靜態(tài)域
例如com.mysql.jdbc.Driver就通過(guò)靜態(tài)代碼塊想DriverManager中注冊(cè)自己。當(dāng)然forName(String name, boolean initialize,ClassLoader loader)也支持指定初始化(initialize)和類(lèi)加載器(loader)
- 通過(guò)
ClassLoader的loadClass()方法加載類(lèi)
4. 類(lèi)加載機(jī)制
4.1 全盤(pán)負(fù)責(zé)
當(dāng)一個(gè)類(lèi)加載器負(fù)責(zé)加載某個(gè)類(lèi)時(shí),該類(lèi)所依賴(lài)的和引用的其他類(lèi)也將由該類(lèi)加載器負(fù)責(zé)加載,除非顯示使用另外一個(gè)類(lèi)加載器來(lái)加載。
4.2 雙親委派
雙親委派(Parent Delegation),是一個(gè)非常糟糕的翻譯,但是因?yàn)槭褂幂^廣所以一直沿用至今, 雙親委派也叫作“父類(lèi)委托”,是指子類(lèi)加載器如果沒(méi)有加載過(guò)該目標(biāo)類(lèi),就先委托父類(lèi)加載器加載該目標(biāo)類(lèi),只有在父類(lèi)加載器找不到字節(jié)碼文件的情況下才從自己的類(lèi)路徑中查找并加載目標(biāo)類(lèi)。
雙親委派的機(jī)制如ClassLoader中l(wèi)oadClass方法所示:
- findLoadedClass,內(nèi)部調(diào)用native方法,在虛擬機(jī)內(nèi)存中查找是否已經(jīng)加載過(guò)此類(lèi)
- 如果沒(méi)有加載,則委派父類(lèi)加載,對(duì)于Extension ClassLoader,parent是null,所以通過(guò)findBootstrapClassOrNull委派Bootstrap ClassLoader加載。
- 如果父類(lèi)沒(méi)有加載,則通過(guò)findClass(name)自己嘗試加載。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
5. 自定義類(lèi)加載器
“雙親委派”機(jī)制只是Java推薦的,并不是強(qiáng)制的機(jī)制。我們可以繼承java.lang.ClassLoader類(lèi),實(shí)現(xiàn)自己的類(lèi)加載器。如果想保持雙親委派模型,只需要重寫(xiě)findClass(name)方法;如果想破壞雙親委派模型,則重寫(xiě)loadClass(name)方法。
如下MyClassLoader,通過(guò)重寫(xiě)findClass,從MyClassLoader.setRoot 指定的目錄加載編譯后的.class 文件。注意文件路徑不能是classpath路徑, 防止要加載的類(lèi)被Application ClassLoader加載。
public class MyClassLoader extends ClassLoader {
private String root;
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] loadClassData(String className) {
// fileName處理邏輯需要根據(jù)實(shí)際情況修改,保證能夠找到文件
String[] name = className.split("\\.");
String fileName = root + File.separatorChar + name[name.length - 1] + ".class";
try {
return Files.readAllBytes(Paths.get(fileName));
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public void setRoot(String root) {
this.root = root;
}
public static void main(String[] args) {
MyClassLoader classLoader = new MyClassLoader();
classLoader.setRoot("指定目錄");
Class<?> testClass = null;
try {
testClass = classLoader.loadClass("類(lèi)的全限定名");
Object object = testClass.newInstance();
System.out.println(object.getClass().getClassLoader());
System.out.println(object.getClass().getClassLoader().getParent());
} catch (Exception e) {
e.printStackTrace();
}
}
}
6. SPI機(jī)制是否打破雙親委派
關(guān)于SPI(Service Provider Interface)是否打破雙親委派,眾說(shuō)紛紜,這里先拋出結(jié)論:沒(méi)有打破雙親委派。
雙親委派機(jī)制是指,子類(lèi)加載器加載類(lèi)之前,先去父類(lèi)加載器中查找,一直查到最基礎(chǔ)的啟動(dòng)類(lèi)加載器,如果都沒(méi)有加載,則嘗試自行加載。根據(jù)雙親委派原理,父類(lèi)加載器加載的類(lèi)對(duì)子類(lèi)可見(jiàn),反之則不成立。
這里以JDBC(Java Database Connectivity,Java數(shù)據(jù)庫(kù)連接池)為例進(jìn)行說(shuō)明。使用JDBC的示例代碼如下:
String url = "jdbc:mysql://localhost:3306/test?serverTimezone=UTC";
Connection conn = DriverManager.getConnection(url, "root", "1234");
1. DriverManager加載
根據(jù)包名可知java.sql.DriverManager是由啟動(dòng)類(lèi)加載器加載,在加載時(shí),通過(guò)靜態(tài)代碼塊調(diào)用loadInitialDrivers()方法, loadInitialDrivers()通過(guò)SPI的方式加載java.sql.Driver的實(shí)現(xiàn),這里是com.mysql.jdbc.Driver和com.mysql.fabric.jdbc.FabricMySQLDriver,加載過(guò)程如下,省略部分非核心代碼:
private static void loadInitialDrivers() {
String drivers;
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
}
2. java.mysql.jdbc.Driver 加載
SPI的機(jī)制這里不展開(kāi)講,核心原理是通過(guò)mysql-connector-java.jar中的META-INF/services/java.sql.Driver文件,找到java.sql.Driver的具體實(shí)現(xiàn),然后加載實(shí)現(xiàn)。
loadInitialDrivers()中的load() 實(shí)現(xiàn)如下所示,注意通過(guò)Thread.currentThread().getContextClassLoader()獲得應(yīng)用類(lèi)加載器,賦值給loader成員變量。
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
SPI中,最終通過(guò)Thread.currentThread().getContextClassLoader()獲得的應(yīng)用類(lèi)加載器加載com.mysql.jdbc.Driver,代碼如下:
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service, "Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service, "Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
}
cn這里就是com.mysql.jdbc.Driver 和com.mysql.fabric.jdbc.FabricMySQLDriver。調(diào)用c.newInstances時(shí), 會(huì)執(zhí)行com.mysql.jdbc.Driver中的靜態(tài)代碼塊,即向DriverManager注冊(cè)自己。
以上穿插的源碼比較多,又夾雜著SPI的源碼,所以比較混亂,這里做一下總結(jié):
-
java.sql.DriverManager是由啟動(dòng)類(lèi)加載器加載,在加載時(shí),通過(guò)SPI加載java.sql.Driver的實(shí)現(xiàn),即com.mysql.jdbc.Driver。 -
com.mysql.jdbc.Driver是由應(yīng)用類(lèi)加載器負(fù)責(zé)加載的 -
父類(lèi)加載器(Bootstrap ClassLoader)通過(guò)線程上下文類(lèi)加載器(ContextClassLoader)去請(qǐng)求子類(lèi)加載器(Application ClassLoader)完成類(lèi)加載的行為,看似打破了雙親委派模型來(lái)逆向使用類(lèi)加載器,晚上所有的打破雙親委派也是指這一過(guò)程。但是子類(lèi)加載器的加載也是走雙親委派流程,先委托給父類(lèi)加載器,加載不到再?lài)L試自行加載,因此完全沒(méi)有破壞雙親委派。
image.png
