1. 類加載的七個階段
1.1加載
- 通過一個類的全限定名來獲取定義此類的二進制字節流;
- 將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構;
- 在內存中生成一個代表這個類的java.lang.Class對象,作爲方法區這個類的各種數據結構的訪問入口;前面說大部分對象都是在堆中創建,但是對於HotSpot虛擬機,這個對象存儲在方法區。
1.2 驗證
- 文件格式驗證
- 元數據驗證
- 字節碼驗證
- 符號引用驗證
1.3 準備
爲類變量分配內存(在方法區)並設置類變量的初始值,通常是零值。
1.4 解析
虛擬機將常量池中的符號引用替換爲直接引用的過程。
1.5 初始化
執行類構造器<clinit>()方法的過程。
1.6 使用
1.7 卸載
2. 類加載器
2.1 雙親委派模型-Parents delegation Model
- 調用 findLoadedClass(String) 檢查這個類是否已經加載過;
- 否則調用父加載器的 loadClass 去加載,如果父加載器爲null,則使用啓動類加載器作爲父加載器Bootstrap Classloader;
- 否則調用自己的 findClass(String) 去加載這個類;
總的來說就是Class文件加載到類加載子系統後,先沿着圖中紅色虛線的方向自下而上進行委託,再沿着黑色虛線的方向自上而下進行查找,整個過程就是先上後下。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
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.
c = findClass(name);
}
}
return c;
}
2.2 自定義ClassLoader
系統提供的類加載器只能夠加載指定目錄下的jar包和Class文件,如果想要加載網絡上的或者是D盤某一文件中的jar包和Class文件則需要自定義ClassLoader。
實現自定義ClassLoader需要兩個步驟:
- 定義一個自定義ClassLoade並繼承抽象類ClassLoader。
- 複寫findClass方法,並在findClass方法中調用defineClass方法。
下面我們就自定義一個ClassLoader用來加載位於D:\lib的Class文件。
2.2.1 編寫測試Class文件
首先編寫測試類並生成Class文件,如下所示。
package com.example;
public class Jobs {
public void say() {
System.out.println("One more thing");
}
}
將這個Jobs.java放入到D:\lib中,使用cmd命令進入D:\lib目錄中,執行Javac Jobs.java對該java文件進行編譯,這時會在D:\lib中生成Jobs.class。
2.2.2 編寫自定義ClassLoader
接下來編寫自定義ClassLoader,如下所示。
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class DiskClassLoader extends ClassLoader {
private String path;
public DiskClassLoader(String path) {
this.path = path;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = null;
byte[] classData = loadClassData(name);//1
if (classData == null) {
throw new ClassNotFoundException();
} else {
clazz= defineClass(name, classData, 0, classData.length);//2
}
return clazz;
}
private byte[] loadClassData(String name) {
String fileName = getFileName(name);
File file = new File(path,fileName);
InputStream in=null;
ByteArrayOutputStream out=null;
try {
in = new FileInputStream(file);
out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length=0;
while ((length = in.read(buffer)) != -1) {
out.write(buffer, 0, length);
}
return out.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(in!=null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try{
if(out!=null) {
out.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
return null;
}
private String getFileName(String name) {
int index = name.lastIndexOf('.');
if(index == -1){//如果沒有找到'.'則直接在末尾添加.class
return name+".class";
}else{
return name.substring(index+1)+".class";
}
}
}
這段代碼有幾點需要注意的,註釋1處的loadClassData方法會獲得class文件的字節碼數組,並在註釋2處調用defineClass方法將class文件的字節碼數組轉爲Class類的實例。loadClassData方法中需要對流進行操作,關閉流的操作要放在finally語句塊中,並且要對in和out分別採用try語句,如果in和out共同在一個try語句中,那麼如果in.close()發生異常,則無法執行 out.close()。
最後我們來驗證DiskClassLoader是否可用,代碼如下所示。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ClassLoaderTest {
public static void main(String[] args) {
DiskClassLoader diskClassLoader = new DiskClassLoader("D:\\lib");//1
try {
Class c = diskClassLoader.loadClass("com.example.Jobs");//2
if (c != null) {
try {
Object obj = c.newInstance();
System.out.println(obj.getClass().getClassLoader());
Method method = c.getDeclaredMethod("say", null);
method.invoke(obj, null);//3
} catch (InstantiationException | IllegalAccessException
| NoSuchMethodException
| SecurityException |
IllegalArgumentException |
InvocationTargetException e) {
e.printStackTrace();
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
註釋1出創建DiskClassLoader並傳入要加載類的路徑,註釋2處加載Class文件,需要注意的是,不要在項目工程中存在名爲com.example.Jobs的Java文件,否則就不會使用DiskClassLoader來加載,而是AppClassLoader來負責加載,這樣我們定義DiskClassLoader就變得毫無意義。接下來在註釋3通過反射來調用Jobs的say方法,打印結果如下:
com.example.DiskClassLoader@4554617c
One more thing
使用了DiskClassLoader來加載Class文件,say方法也正確執行,顯然我們的目的達到了。