這裏大量參考瞭如下博客
https://blog.csdn.net/javazejian/article/details/73413292
ClassLoader是一個抽象類,他有很多個實現
BootstrapClassLoader是最頂層的classloader 負責加載jvm需要的類 <JAVA_HOME>/lib路徑下的核心類庫或-Xbootclasspath參數指定的路徑下的jar包加載到內存中 比如rt.jar 他本身是C++實現的。
ExtClassLoader 是次級的classloader 負責加載<JAVA_HOME>/lib/ext目錄下或者由系統變量-Djava.ext.dir指定位路徑中的類庫,本身是java實現的sun.misc.Launcher$ExtClassLoader。
AppClassLoader 是我們最常用的。它負責加載系統類路徑java -classpath或-D java.class.path 指定路徑下的類庫,本身也是 sun.misc.Launcher$AppClassLoader。
BootstrapClassLoader -》ExtClassLoader -》AppClassLoader 存在這樣的父子關係。
但是這三個類之間本身沒什麼直接繼承關係,他們都是ClassLoader的子類(BootstrapClassLoader除外 它由c++實現),但是通過組合模式 實現了類似父子關係的關係。雙親委派模式
雙親委派模式的實現是依賴於 ClassLoader抽象類定義的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);
} else { // 如果父爲null 說明已經是BootstrapClassLoader了
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();
// 通過findClass 加載 類
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()方法,方法返回Class對象,他是在loadClass父類無法加載時 由自己加載的方法。ClassLoader本身也沒有實現findClass方法 我們的extClassLoader 和 appClassLoader 都是URLClassLoader的子類 而 URLClassLoader 重寫了findClass方法
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
// 替換爲相對 路徑
String path = name.replace('.', '/').concat(".class");
// 轉換成Resource對象 ucp 裏 的path屬性包含了 能夠加載的 類路徑
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
// 轉換成真正的class對象
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
extClassLoader 能夠加載的目錄
appClassLoader能夠加載的目錄
這也就是爲啥靜態覆蓋類能夠生效的原因,因爲classes的加載在 外部jar 包之前。
再來看defineClass
private Class<?> defineClass(String name, Resource res) throws IOException {
long t0 = System.nanoTime();
// 取最後一個點的位置
int i = name.lastIndexOf('.');
// 獲取到剛纔resource 中的真實url地址
URL url = res.getCodeSourceURL();
if (i != -1) {
// 取到 包名
String pkgname = name.substring(0, i);
// Check if package already loaded.
Manifest man = res.getManifest();
// 檢查包名的密封性
definePackageInternal(pkgname, man, url);
}
// Now read the class bytes and define the class
// 從res 中獲取 如果有就用res中的
java.nio.ByteBuffer bb = res.getByteBuffer();
if (bb != null) {
// Use (direct) ByteBuffer:
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
return defineClass(name, bb, cs);
} else {
//獲取類的byte數組
byte[] b = res.getBytes();
// must read certificates AFTER reading bytes.
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
// 真正讀取byte數組並 創建class 對象
return defineClass(name, b, 0, b.length, cs);
}
}
再到ClassLoader抽象類中
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
// 檢查是否能夠進行加載
protectionDomain = preDefineClass(name, protectionDomain);
// 獲取 加載class的 加載地址 比如file:/E:/myWorkSpace/interview/target/classes/
String source = defineClassSourceLocation(protectionDomain);
// 加載class native方法
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
//加載 後置方法
postDefineClass(c, protectionDomain);
return c;
}
private ProtectionDomain preDefineClass(String name,
ProtectionDomain pd)
{
if (!checkName(name))
throw new NoClassDefFoundError("IllegalName: " + name);
// Note: Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
// relies on the fact that spoofing is impossible if a class has a name
// of the form "java.*"
// 包名不能以java開頭
if ((name != null) && name.startsWith("java.")) {
throw new SecurityException
("Prohibited package name: " +
name.substring(0, name.lastIndexOf('.')));
}
if (pd == null) {
pd = defaultDomain;
}
if (name != null) checkCerts(name, pd.getCodeSource());
return pd;
}
所以loadClass 是實現雙親委派機制 而findClass纔是真正加載類的地方。
ExtClassLoader 和 AppClassLoader 都是Launcher的內部類 他們是在Launcher 的構造方法中創建的
Launcher
public Launcher() {
// 創建extClassLoader
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
// 創建AppClassLoader
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
// 給線程上下文設置ClassLoader 爲 appClassLoader
Thread.currentThread().setContextClassLoader(this.loader);
// 這裏設置安全檢查 默認爲 null 不創建
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);
}
}
違反雙親委派
1.調用findClass方法
2.繼承URLClassLoader 如果重寫了 loadClass方法並且 不再實現雙親委派的機制 或者直接調用了 findClass方法
違反雙親委派的用途
1.熱部署
2.自定義地址class加載(從非classPath中加載)
3.SPI
包名爲 java.開頭的類是不允許加載的 這時如果 rt.jar 裏的某個接口需要第三方提供實現(比如sql.Driver) 由於rt.jar 是由 bootstrapClassLoader 加載的 但是它又不能加載第三方jar包,這時怎麼辦 就需要違反雙親委派機制 由 bootstrapClassLoader 委派 appClassLoader來進行加載
//DriverManager是Java核心包rt.jar的類
public class DriverManager {
//省略不必要的代碼
static {
loadInitialDrivers();//執行該方法
println("JDBC DriverManager initialized");
}
//loadInitialDrivers方法
private static void loadInitialDrivers() {
sun.misc.Providers()
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//加載外部的Driver的實現類
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;
}
});
}
public static <S> ServiceLoader<S> load(Class<S> service) {
// 這裏就獲取到了我們之前Launcher註冊的 線程上下文加載器 也就是appClassLoader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
// 這裏創建了一個ServiceLoader 保存了 信息
return ServiceLoader.load(service, cl);
}
// 這裏的next java.util.ServiceLoader#iterator#next
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
// 會進入這裏
return lookupIterator.next();
}
// 這裏是 java.util.ServiceLoader.LazyIterator #next
public S next() {
if (acc == null) {
//進入這裏
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
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;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
這就實現了委託appClassLoader給bootstrapClassLoader加載類信息 也就是爲何提供了違反雙親委派的方法。