類的加載是指將類的.class文件中二進制數據讀入到內存中,而後將其放在運行時數據區的方法區
內,而後在內存中建立愛你一個java.lang.Class
對象java
規範並無說明Class對象應該存放在哪,HotSpot虛擬機將其放在方法區中,用來封裝類在方法區內的數據結構mysql
servlet技術sql
類加載器用來把類加載到Java虛擬機中,從JDK1.2版本開始,類的加載過程採用雙親委託機制
,這種機制能保證Java平臺的安全性.數據庫
從源碼文檔中翻譯應該稱爲父類委託模式bootstrap
類加載器並不須要等到某個類被首次主動使用
時再加載它數組
程序首次主動
使用該類時才報告錯誤(LinkageError)根加載器沒有父加載器
,主要負責虛擬機的核心類庫,如java.lang.*
等,java.lang.Object
是由根類加載器加載的,根類加載器的實現依賴於底層操做系統
,屬於虛擬機實現第一部分,它並無繼承java.lang.ClassLoader類. 啓動類加載器是特定於平臺的機器指令,它負責開啓整個加載過程 啓動類加載器還會負責加載JRE正常運行所需的基本組件.其中包括java.util
,java.lang
包中的類安全
擴展類加載器的父加載器是根加載器
,從java.ext.dirs
系統屬性指定的目錄中加載類庫,或者再jre\lib\ext
子目錄下加載類庫,若是把用戶建立的JAR文件放在這個目錄下,會自動由擴展類加載器
加載,擴展類加載器是純Java類,是ClassLoader的子類bash
注意一點的是,拓展類加載器加載的是jar包內的class文件網絡
系統類加載器
的父加載器爲擴展類加載器
,從環境變量classpath或者系統屬性java.class.path
所制定的目錄加載類,它是用戶自定義的類加載器的默認父加載器,系統類加載器是純Java類,是ClassLoader的子類數據結構
除了虛擬機自帶的加載器外,用戶能夠定製本身的類加載器.Java提供了抽象類ClassLoader.全部用戶自定義的加載器都應該繼承ClassLoader
AppClassLoader和ExtClassLoader都是Java類,因此須要類加載器進行加載,而這兩個類的類加載器就是bootstrapClassLoader
能夠經過修改 System.getProperty(java.system.class.loader)對默認的SystemClassLoader進行修改
在父親委託機制中,各個加載器按照父子關係造成樹形結構,除了根加載器以外,其他的類加載器有且只有一個父加載器.
簡單描述,就是一個類加載器要加載一個類,並非由自身進行直接加載,而是經過向上尋找父加載器,直到沒有父加載器的類加載器,而後再從上至下嘗試加載,直至找到一個能夠正確加載的類加載器,通常狀況下,系統類加載器就能加載普通的類.
並非全部的類加載器都必須遵照雙親委託的機制,具體實現能夠根據須要進行改造
public class Test08 {
public static void main(String[] args) {
try {
Class<?> clzz = Class.forName("java.lang.String");
//若是返回null,證實是由BootStrap加載器進行加載的
System.out.println(clzz.getClassLoader());
Class<?> customClass = Class.forName("com.r09er.jvm.classloader.Custom");
System.out.println(customClass.getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class Custom{
}
複製代碼
輸出
null
sun.misc.Launcher$AppClassLoader@18b4aac2
複製代碼
String的類加載器爲null,證實String是由Bootstrap類加載器加載,由於根加載器是由C++實現.因此會返回null
.
Custom的類加載器是Launcher$AppClassLoader,這個類是不開源的.可是是默認的系統(應用)類加載器
.
經過ClassLoader手動加載類,觀察是否會觸發類的初始化
public class Test12 {
public static void main(String[] args) throws Exception {
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class<?> aClass = loader.loadClass("com.r09er.jvm.classloader.TestClassLoader");
System.out.println(aClass);
System.out.println("-------");
aClass = Class.forName("com.r09er.jvm.classloader.TestClassLoader");
System.out.println(aClass);
}
}
class TestClassLoader{
static {
System.out.println("Test classloader");
}
}
複製代碼
輸出
class com.r09er.jvm.classloader.TestClassLoader
-------
Test classloader
class com.r09er.jvm.classloader.TestClassLoader
複製代碼
結論
明顯能夠看出,classLoader.load方法加載類,類並不會初始化,說明不是對類的主動使用,調用了Class.ForName
才進行初始化
打印類加載器,因爲根加載器由C++編寫,因此就會返回null
public static void main(String[] args) {
ClassLoader loader = ClassLoader.getSystemClassLoader();
System.out.println(loader);
//向上遍歷父classLoader
while (null != loader) {
loader = loader.getParent();
System.out.println(loader);
}
}
複製代碼
輸出結果
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@610455d6
null
複製代碼
clazz.getClassLoader()
Thread.currentThread().getContextLoader()
ClassLoader.getSystemClassLoader()
DriverManager.getClassLoader()
類加載器是負責加載類
的對象,classLoader是抽象類.賦予類一個二進制名稱,一個類加載器應當嘗試定位
或生成
數據,這些數據構成類的定義.一種典型的策略是將二進制名稱轉換爲文件名,而後從文件系統中讀取該名稱的字節碼文件
。
每個類
對象都包含定義該類
的classLoader引用(reference)
數組
對應的class對象並非由類加載器建立的,而是由java虛擬機在須要時自動建立的.對於一個數組的類加載器,與這個數組元素的類加載器一致.若是數組是原生類型
,那這個數組將沒有classLoader
String[],則這個數組的類加載器是String的類加載器,使用的是Bootstrap類加載器 int[] ,這種基本類型的數組,是沒有類加載器的.
應用
實現classLoader的目的是爲了拓展JVM動態加載類
ClassLoader使用了委託模型去尋找類的資源.ClassLoader的每個實例都有會一個關聯的父ClassLoader,當須要尋找一個類的資源時,ClassLoader實例就會委託給父ClassLoader.虛擬機內建的ClassLoader稱爲BootstrapClassLoader
,BootstrapClassLoader
自己是沒有父ClassLoader的,可是能夠做爲其餘ClassLoader的父加載器
支持併發加載的類加載器稱爲並行類加載器,這種類加載器要求在類初始化期間經過ClassLoader.registerAsParallelCapable
將自身註冊上.默認狀況下就是並行的,而子類須要須要並行,則須要調用該方法
在委託機制並非嚴格層次化的環境下,classLoader須要並行處理,不然類在加載過程當中會致使死鎖,由於類加載過程當中是持有鎖的
一般狀況下,JVM會從本地的文件系統中加載類,這種加載與平臺相關.例如在UNIX系統中,jvm會從環境變量中CLASSPATH定義的目錄中加載類.
然而有些類並非文件,例如網絡,或者由應用構建出來(動態代理),這種狀況下,defineClass
方法會將字節數組轉換爲Class實例,能夠經過Class.newInstance
建立類真正的對象 由類加載器建立的對象的構造方法和方法,可能會引用其餘的類,因此JVM會調用loadClass
方法加載其餘引用的類
二進制名稱BinaryNames
,做爲ClassLoader中方法的String參數提供的任何類名稱,都必須是Java語言規範所定義的二進制名稱。 例如
步驟
loadClass
方法loadClass
方法中實現加載class字節碼的方法,返回byte[]super.defineClass(byte[])
方法將Class對象返回給loadClass方法源碼示例
public class Test16 extends ClassLoader {
private String classLoaderName;
private String path;
private final String fileExtension = ".class";
public Test16(String classLoaderName) {
//將systemClassLoader做爲當前加載器的父加載器
super();
this.classLoaderName = classLoaderName;
}
public Test16(ClassLoader parent, String classLoaderName) {
//將自定義的ClassLoader做爲當前加載器的父加載器
super(parent);
this.classLoaderName = classLoaderName;
}
public void setPath(String path) {
this.path = path;
}
public static void main(String[] args) throws Exception {
Test16 loader1 = new Test16("loader1");
//設置絕對路徑,加載工程根目錄下的com.r09er.jvm.classloader.Test01.class
loader1.setPath("/Users/cicinnus/Documents/sources/jvm-learning/");
Class<?> aClass = loader1.loadClass("com.r09er.jvm.classloader.Test01");
//打印加載的類
System.out.println("loader1 load class" + aClass.hashCode());
Object instance = aClass.newInstance();
System.out.println("instance1: " + instance);
Test16 loader2 = new Test16("loader2");
//設置絕對路徑,加載工程根目錄下的Test01.class
loader2.setPath("/Users/cicinnus/Documents/sources/jvm-learning/");
Class<?> aClass2 = loader2.loadClass("com.r09er.jvm.classloader.Test01");
System.out.println("loader2 load class" + aClass2.hashCode());
Object instance2 = aClass2.newInstance();
System.out.println("instance2 : " + instance2);
//todo ****
//1.從新編譯工程,確保默認的classPath目錄下有Test01.class的字節碼文件,而後運行main方法,觀察輸出
//2.刪除默認classpath目錄下的Test01.class,運行main方法,觀察輸出
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("invoke findClass");
System.out.println("class loader name : " + this.classLoaderName);
byte[] bytes = this.loadClassData(name);
return super.defineClass(name, bytes, 0, bytes.length);
}
private byte[] loadClassData(String binaryName) {
byte[] data = null;
binaryName = binaryName.replace(".", "/");
try (
InputStream ins = new FileInputStream(new File(this.path + binaryName + this.fileExtension));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
) {
int ch;
while (-1 != (ch = ins.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return data;
}
}
複製代碼
執行兩次main方法後,會發現類加載器真正生效的邏輯,由於默認的父加載器實際上是系統加載器(AppClassLoader
),因此若是默認的classPath存在字節碼文件,則會由AppClassLoader
正確加載類,若是classPath中沒有,則會向下使用自定義的類加載器加載類
若是構造函數傳入兩個不同的ClassLoaderName,會發現兩個class對象並不一致,是因爲命名空間NameSpace
的緣由,由於兩個類加載器定義的名稱是不同的,若是改爲相同的名稱,則兩個class對象一致
重寫的是findClass方法,在調用時候,使用的是classLoader的
loadClass
方法,這個方法內部會調用findClass
還有一個重點,若是將class字節碼文件放在根目錄,則會拋出
NoClassDefFoundError
異常,由於binaryName
不符合規範.
實現本身的類加載器,最重要就是實現findClass,經過傳入binaryName
,將二進制名稱加載成一個Class對象
在實現findClass
後,須要經過defineClass方法,將二進制數據交給defineClass
方法轉換成一個Class實例, 在defineClass
內部會作一些保護和檢驗工做.
經過loadClass
方法加載類,會有以下默認加載順序
findLoadedClass
方法檢查class是否被加載loadClass
方法,若是父加載器爲null,則會調用JVM內建的類加載器.findClass
方法找到類在默認的loadClass方法中,類加載是同步
的
雙親委派機制優勢
同一個類(包名,類名一致)
,命名空間發揮的做用
由於優先加載的是類庫中的class,會忽略掉自定義的類
當類被加載,鏈接,初始化以後,它的生命週期就開始了.當表明類的Class對象再也不被引用,即不可觸及時,Class對象就會結束生命週期,類在元空間內的數據也會被卸載,從而結束類的生命週期.
一個類什麼時候結束生命週期,取決於表明它的Class對象什麼時候結束生命週期
由Java虛擬機自帶的類加載器所加載的類,在虛擬機的生命週期中,始終不會被卸載.
用戶自定義的類加載器所加載的類是能夠被卸載的
BootstrapClassLoader加載的路徑
ExtClassLoader
AppClassLoader
三個路徑和JDK版本,操做系統都有關係
若是將編譯好的class字節碼文件放到根加載器的加載路徑上,能夠成功由BootstrapClassLoader加載
即子加載器能訪問父加載器加載的類,而父加載器不能訪問子加載器加載的類.(相似於繼承的概念)
一個Java類是由該類的全限定名稱+用於加載該類的定義類加載器(defining loader)共同決定.
ClassLoader.getSystemClassLoader
源碼返回用於委託的系統類加載器.是自定義類加載器的父加載器,一般狀況下類會被系統類加載器加載. 該方法在程序運很早的時間就會被建立,而且會將系統類加載器設爲調用線程的上下文類加載器(context class loader
)
1.初始化ExtClassLoader 2.初始化AppClassLoader,將初始化好的ExtClassLoader設置爲AppClassLoader的父加載器 3.將AppClassLoader設置爲當前線程的上下文類加載器
SystemClassLoaderAction
邏輯1.判斷System.getProperty("java.system.class.loader")
是否有設置系統類加載器 2.若是爲空,直接返回AppClassLoader
3.若是不爲空,經過反射建立classLoader,其中必須提供一個函數簽名爲ClassLoader
的構造 4.將反射建立的自定義類加載器設置爲上限爲加載器. 5.返回建立好的類加載器
Class.ForName(name,initialize,classloader)
解析name
,須要構造的類全限定名稱(binaryName)不能用於原生類型或者void類型 若是表示的是數組,則會加載數組中的元素class對象,可是不進行初始化
initialize
,類是否須要初始化classloader
,加載此類的類加載器ContextClassLoader
)實現與分析CurrentClassLoader(當前類加載器)
ClassLoader
去加載當前類引用的其餘類若是ClassA引用了ClassY,那麼ClassA的類加載器會去加載ClassY,前提是ClassY未被加載
線程類加載器從JDK1.2開始引入,Thread類中的getContextClassLoader
和setContextClassLoader
分別用來獲取和設置上下文加載器.若是沒有手動進行設置,那麼線程會繼承其父線程的上下文加載器. java應用運行時的初始線程的上下文類加載器是系統類加載器(AppClassLoader),在線程中運行的類能夠經過這個類加載器加載類與資源
回顧一下JDBC操做
Class.forName("com.mysql.driver.Driver");
Connection conn = Driver.connect();
Statement stae = conn.getStatement();
複製代碼
Driver
,Connection
,Statement
都是由JDK提供的標準,而實現是由具體的DB廠商提供. 根據類加載的機制,JDK的rt包會被BootstrapClassLoader
加載,而自定義的類會被AppClassLoader
加載,同時由於命名空間
的緣由,父加載器是沒法訪問子加載器加載的類的.因此父加載器會致使這個問題.
上下文加載器就是爲了解決這種問題所存在的
父ClassLaoder可使用當前線程Thread.currentThread().getContextClassLoader()
加載的類, 這就改變了父ClassLoader不能使用子ClassLoader或是其餘沒有直接父子關係的ClassLoader沒法訪問對方加載的class問題.
即改變了父親委託模型
使用步驟(獲取 - 使用 - 還原)
ContextClassLoader的做用就是破壞Java的類加載委託機制
ServiceLoader
是一個簡單的服務提供者加載設施
加載基於JDK規範接口實現的具體實現類 實現類須要提供無參構造,用於反射構造出示例對象
服務提供者將配置文件放到資源目錄的META-INF/services
目錄下,告訴JDK在此目錄的文件內配置了須要加載的類,其中文件名稱是須要加載的接口全限定名稱,文件內容是一個或多個實現的類全限定名稱.
在雙親委託模型下,類加載時由下至上的.可是對於SPI
機制來講,有些接口是由Java核心庫提供的,根據類加載的機制,JDK的rt包會被BootstrapClassLoader
加載,而自定義的類會被AppClassLoader
加載.這樣傳統的雙親委託模型就不能知足SPI
的狀況,就能夠經過線程上下文加載器來實現對於接口實現類的加載.