介紹
Java是一個純面向對象的語言,Java的體系結構是由一個一個的類構成的。類加載是將.class文件定義為JVM中一個類的過程,也是使用一個類的前提條件。每一個類由:它的全限定名+它的類加載器
唯一確定。
類加載器是一個抽象類:abstract ClassLoader。JDK給我們實現了三個類加載器,BootStrapClassLoader、ExtClassLoader、AppClassLoader。
雙親委派機制
如果一個類加載器收到了類加載請求,它並不會自己先去加載,而是把這個請求委托給父類的加載器去執行,如果父類加載器還存在其父類加載器,則進一步向上委托,依次遞歸,請求最終將到達頂層的啟動類加載器,如果父類加載器可以完成類加載任務,就加載后返回,否則交給子類加載器完成。
特點
- 安全,可避免用戶自己編寫的類動態替換Java的核心類,如java.lang.String
- 避免全限定命名的類重復加載,使用了findLoadClass()判斷當前類是否已加載
代碼分析
loadClass
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);
// 沒爹就找BootStrap加載器去加載
} 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;
}
}
findClass
這個方法是上面第29行被調用的方法。如果想保持雙親委派機制,在自定義類加載器的時候不去覆蓋loadClass,去覆蓋findClass就可以了。
自定義類加載器的例子
package org.zzj;
public class User {
}
package org.zzj;
public class UserService {
public void add() {
try {
System.out.println(Class.forName("org.zzj.User").getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
package org.zzj;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
public class ClassForNameTest {
public static void main(String[] args) throws Exception {
// 當前調用者的加載器是 sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(Class.forName("org.zzj.User").getClassLoader());
MyClassLoader classLoader = new MyClassLoader();
// clazz是 org.zzj.MyClassLoader@5b2133b1 加載的
Class<?> clazz = classLoader.loadClass("org.zzj.UserService");
System.out.println("aaa -> " + clazz.getClassLoader());
Method method = clazz.getMethod("add");
method.invoke(clazz.newInstance());
}
}
class MyClassLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
System.out.println(name);
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream in = getClass().getResourceAsStream(fileName);
if (in == null) {
return super.loadClass(name);
}
byte[] b = null;
try {
b = new byte[in.available()];
in.read(b);
in.close();
} catch (IOException e) {
e.printStackTrace();
}
// 將 字節數組 轉為 類
return defineClass(name, b, 0, b.length);
}
}
Class.forName和ClassLoader.loadClass的區別
Class.forName加載的類會進行初始化,而ClassLoader.loadClass加載的類不會進行初始化:
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
需要注意的是這個使用這個方法加載得到的類默認是調用者的類加載器。
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
線程上下文類加載器
Java 提供了很多服務提供者接口(Service Provider Interface,SPI),允許第三方為這些接口提供實現。常見的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。
這些 SPI 的接口由 Java 核心庫來提供,而這些 SPI 的實現代碼則是作為 Java 應用所依賴的 jar 包被包含進類路徑(CLASSPATH)里。SPI接口中的代碼經常需要加載具體的實現類。那么問題來了,SPI的接口是Java核心庫的一部分,是由啟動類加載器(Bootstrap Classloader)來加載的;SPI的實現類是由系統類加載器(System ClassLoader)來加載的。引導類加載器是無法找到 SPI 的實現類的,因為依照雙親委派模型,BootstrapClassloader無法委派AppClassLoader來加載類。
而線程上下文類加載器破壞了“雙親委派模型”,可以在執行線程中拋棄雙親委派加載鏈模式,使程序可以逆向使用類加載器。