什麼是類的加載
我們平時所編寫的“xx.java”文件需要經過我們所知的java編譯器(javac)編譯成“xx.class”文件,這個文件存放着編譯後jvm指令的的二進制信息。而**當我們需要用到某個類時,jvm將會加載它,並在內存中創建對應的class對象,這個過程稱之爲類的加載。**過程如下:
類的加載、連接、初始化
1. 加載
通過類的包名和雷鳴查找到此類的字節碼文件,將xx.class文件中的二進制數據讀入到jvm內存,並存入其中的方法區內,然後利用字節碼文件創建一個class對象存入到堆之中,用來封裝類的數據結構等。
連接(驗證、準備、解析):
2. 連接包括三步:
驗證:確保被加載類的正確性,是否對jvm有害
準備:爲**類的靜態變量(static)**分配內存並將其賦值爲默認變量(非賦值)eg:static int a = 1;(爲a分配內存並設置爲0,將a賦值爲1的動作是在初始化過程中完成的,而final static int a = 1賦值是在javac過程中完成的。)
解析:把類中的符號引用轉化爲直接引用,符號引用:我們所寫的int a 就是符號引用,直接引用:目標的指針、偏移量內存中的引用,類似c/c++中的指針。
3. 初始化
如果有父類,此時加載父類並進行初始化,靜態變量、成員變量等,再加載子類。
類加載器
類的加載是由類加載器完成的。類加載器分爲jvm自帶的類加載器和用戶自定義的類加載器。
jvm內置加載器():
啓動類加載器Bootstrap ClassLoader
擴展類加載器Extention ClassLoader
系統類加載器System(Application) ClassLoader(應用類加載器)
用戶自定義加載器:
java.lang.ClassLoader的子類實例。
Bootstrap ClassLoader
最底層的類加載器,是jvm的一部分,由c++實現,沒有父加載器
主要負責加載由系統屬性sun.boot.class.path指定的路徑下的核心類庫(jre/lib),出於安全考慮該類加載器只加載只加載java,javax,sun相關包裏的類。
Extention ClassLoader
sun公司實現的sun.misc.Launcher$ExtClassLoader類(jdk8),由java編寫,負責加載jre/lib/ext目錄下的類庫或者由系統變量“java.ext.dirs”指定的目錄下的類庫。父加載器是Bootstrap ClassLoader(非繼承)。
System(Application) ClassLoader
一個純java類,sun公司實現的sun.misc.Launcher$AppClassLoader類(jdk8),父加載器是擴展加載器(Extention ClassLoader)(非繼承),負責從classpath環境變量或者java.class.path所指定的目錄中加載類。是用戶自定義的類加載器的默認父加載器。一般情況下是程序默認類加載器,可通過*ClassLoader.getSystemClassLoader()*直接獲得。
雙親委派模型
雙親委派機制
當需要使用該類時,纔會將它的class文件加載到內存生成class對象(按需加載),加載時,使用的是雙親委派模式:如果一個類加載器收到了類請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父加載器去完成,每一層都是如此,因此所有類加載的請求都會傳到啓動類加載器,只有當父加載器無法完成該請求時,子加載器纔去自己加載。(父加載器、子加載器:非繼承關係,而是用組合模式來複用父加載器代碼)
雙親委派機制的優點:
- 避免類的重複加載,當父加載器加載成功時,就沒必要子加載器再去加載
- 安全。防止java核心API不會被隨意替換。比如一個網絡上的java.lang.Object類,無論哪個類加載器去加載該類,最終都是由啓動類加載器進行加載的,因此Object類在程序的各種類加載環境中都是一個類。如果不委託,那麼java.lang.Object類存放在classpath中,那麼系統中就會出現多個Object類,程序變得很混亂。
ClassLoader
除了啓動類加載器Bootstrap ClassLoader,其它類加載器都必須繼承java.lang.ClassLoader(abstract)
主要方法:
loadClass();!!!不要覆寫此方法
findClass();
defineClass();
resolveClass();
相關使用參考此處
URLClassLoader
java.net.URLClassLoader,擴展了ClassLoader。可以從本地、網絡上的指定url加載類。可使用該類作爲自定義的類加載器使用。
- 加載磁盤上的class文件:
現在有一class文件 F:/Test01.class
Test01中有默認構造方法:
public Test01(){
System.out.println("test01");
}
此時我們利用URLClassLoader就可以加載磁盤上的class文件:
File file = new File("F:/");
URI uri = file.toURI();
URL url = uri.toURL();
URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
System.out.println(classLoader.getParent()); //父加載器
Class aClass = classLoader.loadClass("Test01");
aClass.newInstance();//驗證是否調用默認構造方法
對應輸出:
可以看到默認構造方法已經被調用。
- 加載網絡上的class文件
類似,請讀者自行測試
自定義類加載器
方法:繼承ClassLoader,並覆寫findClass()方法。
自定義文件類加載器
這裏同上面一樣,存在 F:/Test01.class
我們自定義一個文件類加載器來實現這個類的加載:
import java.io.*;
/*
* 自定義本地類加載器
* director 類所在的目錄
*/
public class MyFileClassLoader extends ClassLoader{
private String director;
//默認父加載器爲應用類加載器(ApplicationClassLoader)
public MyFileClassLoader(String director) {
this.director = director;
}
//指定父加載器
public MyFileClassLoader(String director, ClassLoader parentClassLoader) {
super(parentClassLoader);
this.director = director;
}
/**
* @param name 類名
* @return 返回字節碼對象
*/
@Override
protected Class<?> findClass(String name) {
try {
//將類轉化爲真正對應class文件的目錄
String file = director + File.separator + name.replace(".", "/") + ".class";
//構建輸入輸出流
InputStream is = new FileInputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
int len = -1;
while ((len = is.read(bytes))!= -1) {
baos.write(bytes, 0, len);
}
//讀取到的字節碼的二進制數據
byte[] data = baos.toByteArray();
is.close();
baos.close();
/*
* name - 預期的 binary name的類
* data - 構成類數據的字節。
* 0 - 類數據中的起始偏移量爲 0
* data.length - 類數據的長度
*/
return defineClass(name, data, 0, data.length);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception {
MyFileClassLoader myClassLoader = new MyFileClassLoader("F:/");
Class aClass = myClassLoader.loadClass("Test01");
aClass.newInstance();
}
}
網絡類加載器
網絡類加載器類似(相關流API的使用)
熱部署類加載器
**避開雙親委派模式,實現一個類的不同類加載器的加載。**我們已經知道雙親委派模式主要是以loadClass()方法實現的,那麼我們可以不使用loadClass()而是用findClass()方法,避開雙親委派,實現一個類的多次加載,達到熱部署。
public static void main(String[] args) throws Exception {
MyFileClassLoader myClassLoader = new MyFileClassLoader("F:/");
MyFileClassLoader myClassLoader2 = new MyFileClassLoader("F:/", myClassLoader);
Class aClass = myClassLoader.findClass("Test01");
Class aClass2 = myClassLoader2.findClass("Test01");
System.out.println(aClass.hashCode());
System.out.println(aClass2.hashCode());
}
輸出結果:
455896770
1323165413
可以看到哈希碼不同,證明一個類被兩個加載器都加載了。