我們都知道,Java類都是靠ClassLoader來加載的,而類加載器也是java類,因而java類加載器本身也要被類加載器加載,顯然必須有第一個類加載器不是java類,這個加載器正是BootstrapClassLoader。由於它不是Java類,因此它不需要被別人加載,而嵌套在Java虛擬機內核裏面,也就是JVM啓動的時候Bootstrap就已經啓動,它是用C++寫的二進制代碼(不是字節碼),它可以去加載別的類。
我們還知道,JAVA虛擬機的ClassLoader採用“雙親委派”機制,如下圖所示:
圖中BootstrapClassLoader、ExtensionClassLoader 和 AppClassLoader是JVM中內置了三個重要的 ClassLoader,其中:
BootstrapClassLoader 負責加載 JVM 運行時核心類,這些類位於 JAVA_HOME/lib/rt.jar 文件中,我們常用內置庫 java.xxx.* 都在裏面,比如 java.util.*、java.io.*、java.nio.*、java.lang.* 等等。這個 ClassLoader 比較特殊,它是由 C 代碼實現的,我們將它稱之爲“根加載器”。
ExtensionClassLoader 負責加載 JVM 擴展類,比如 swing 系列、內置的 js 引擎、xml 解析器 等等,這些庫名通常以 javax 開頭,它們的 jar 包位於 JAVA_HOME/lib/ext/*.jar 中,有很多 jar 包。
AppClassLoader 纔是直接面向我們用戶的加載器,它會加載 Classpath 環境變量裏定義的路徑中的 jar 包和目錄。我們自己編寫的代碼以及使用的第三方 jar 包通常都是由它來加載的。
所謂“雙親委派”就是指當前類加載器在決定加載一個類之前,不是直接自己加載,而是先委派其父類來加載,如果其父類能加載該類,則直接返回父類加載的類,否則纔會自行加載。
此外,參考博客老大難的 Java ClassLoader,到了該徹底理解它的時候了中還有如下一段描述:
圖中的 ExtensionClassLoader 的 parent 指針畫了虛線,這是因爲它的 parent 的值是 null,當 parent 字段是 null 時就表示它的父加載器是「根加載器」。如果某個 Class 對象的 classLoader 屬性值是 null,那麼就表示這個類也是“根加載器”加載的。
看到這段話我不禁疑惑,既然ExtensionClassLoader的parent爲null,那麼ExtensionClassLoader是如何委派給BootstrapClassLoader呢?直到我看了ClassLoader.java這個類的源碼:
public abstract class ClassLoader {
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;
}
}
private Class<?> findBootstrapClassOrNull(String name)
{
if (!checkName(name)) return null;
return findBootstrapClass(name);
}
// return null if not found
private native Class<?> findBootstrapClass(String name);
}
由源碼可知,在ClassLoader的loadClass方法中,當前的ClassLoader的parent如果爲null,則會調用其自定義的findBootstrapClassOrNull方法,而後者最終調用了findBootstrapClass方法。該方法爲native方法,由該方法名可以知道,該方法就是用於查找BootstrapClassLoader加載的Class。
至於爲什麼要將ExtensionClassLoader的parent設計爲null,由前面的介紹就很容易理解了:BootstrapClassLoader不是Java類,而是直接嵌套在Java虛擬機內核裏面,因而ExtensionClassLoader無法引用BootstrapClassLoader。
最後補充一下類加載器的命名空間:每個類加載器對應一個命名空間,命名空間起到了一個類相互隔離的作用。而關於不同類加載器對應的命名空間中的類之間的可見性如下(參考):
- 同一個命名空間內的類是相互可以見的
- 子加載器的命名空間包含所有父加載器的命名空間。因此由子加載器加載的類能看見父加載器加載的類。
- 由父加載器加載的類不能看見子加載器加載的類。
- 如果兩個加載器之間沒有直接或間接的父子關係,那麼它們各自加載的類相互不可見
參考博客:
1、http://blog.itpub.net/31561269/viewspace-2222522/ 老大難的 Java ClassLoader,到了該徹底理解它的時候了
2、https://www.jianshu.com/p/2000f9d805ef Java類的加載和初始化
3、https://blog.csdn.net/daochuwenziyao/article/details/77689154 類加載器簡述
4、https://www.ibm.com/developerworks/cn/java/j-lo-classloader/ 深入探討 Java 類加載器
5、https://www.cnblogs.com/lanxuezaipiao/p/4138511.html 關於java類加載雙親委派機制的思考
6、https://blog.csdn.net/weixin_43258908/article/details/89094291 Java類的加載方式、類的初始化、類的執行方式
7、https://www.jianshu.com/p/2840e87de2b7 Java類加載器