類加載器是如何加載一個類的? 類加載器如何實現並行加載類? 帶着這2個問題,我們看下面的內容。
類加載過程實戰
首先,準備一段測試代碼,代碼中使用應用程序類加載器去嘗試加載一個類
public class ClassLoaderDemo {
public static void main(String[] args) throws Exception {
System.out.println();
Class zclass =
ClassLoader.getSystemClassLoader().loadClass("huangy.hyinterface.Generator");
System.out.println(zclass);
}
}
通過打斷點,我們發現最開始是使用應用程序類加載器進行加載
在應用程序類加載器中,首先會嘗試讓其父類加載器(即擴展類加載器)去嘗試加載類
在擴展類加載器中,嘗試讓啓動類加載器去加載器類
類加載過程源碼解析
上面就是標準的雙親委派模型。其具體源碼解析如下:
// 代碼位置 java.lang.ClassLoader#loadClass(java.lang.String, boolean)
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
/*
* 獲取鎖進行同步
* 類加載器加載類是會同步的,只不過看同步的粒度是整體串行、還是分段同步
*/
synchronized (getClassLoadingLock(name)) {
// 首先,檢查該類是否已經被加載了,加載了則直接返回
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 如果有父類加載器,首先讓父類加載去加載類,即我們常說的雙親委派模型
c = parent.loadClass(name, false);
} else {
/*
* 如果沒有父類加載器,則讓啓動類加載器進行加載
* 通過調試可知,擴展類加載器ExtClassLoader的parent屬性爲null,則會讓
* 啓動類加載器進行加載類
*/
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;
}
}
protected Object getClassLoadingLock(String className) {
// 默認情況下,類加載器本身作爲鎖,只能串行加載
Object lock = this;
if (parallelLockMap != null) {
// 如果註冊成功了,則支持分段鎖,即支持並行加載
Object newLock = new Object();
// 對同一個類,返回同一把鎖
lock = parallelLockMap.putIfAbsent(className, newLock);
if (lock == null) {
lock = newLock;
}
}
return lock;
}
注意上述代碼中,parallelLockMap只有在註冊了的情況下,爲不爲null,也就是可以並行加載類,那麼具體的註冊流程是怎樣的?代碼如下:
// 代碼位置 java.lang.ClassLoader.ParallelLoaders#register
/**
* 要顯示的調用ParallelLoaders#register方法,纔會進行註冊。
* 註冊當前類加載器,其父類加載器必須支持並行加載,才能註冊成功,這個爲什麼?
* 理由很簡單,因爲走的是雙親委派模型,假如子類加載器支持並行,但是父類加載器是串行,那麼實際上整個類加載過程就還是串行的。
*/
static boolean register(Class<? extends ClassLoader> c) {
synchronized (loaderTypes) {
if (loaderTypes.contains(c.getSuperclass())) {
loaderTypes.add(c);
return true;
} else {
return false;
}
}
}
/**
* 判斷是否支持註冊
*/
static boolean isRegistered(Class<? extends ClassLoader> c) {
synchronized (loaderTypes) {
return loaderTypes.contains(c);
}
}
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
// 如果註冊過了,則初始化parallelLockMap,則支持並行加載
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
並行加載類實戰
/**
* 並行加載類示例
* @author huangy on 2019-10-22
*/
public class CustomClassLoaderDemo {
public static void main(String[] args) throws Exception {
// 自定義類加載器
ClassLoader customClassLoader = new CustomClassLoader();
Class zclass = customClassLoader.loadClass("huangy.hyinterface.Generator");
System.out.println(zclass);
}
}
class CustomClassLoader extends ClassLoader {
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 這裏加載二進制字節流的方式可以自己決定
return super.loadClass(name, resolve);
}
static {
// 註冊,讓類加載器支持並行加載類
ClassLoader.registerAsParallelCapable();
}
}
如圖,註冊之後,parallelLockMap不爲空,即獲取的是類粒度的分段鎖,從而實現了類的並行加載。
總結一下
- 使用類加載器加載類,則首先會嘗試讓父類加載器去加載類,如果找不到,再由當前類加載器去加載類
- 加載類的時候,默認是串行的,因爲使用類加載器自身作爲鎖
- 如果先進行類的註冊,則能實現類的並行加載,從提高程序的啓動速度