1). 字節(jié)碼和.class文件區(qū)別
新建Java對象時(shí)谷羞,JVM將這個對象對應(yīng)的字節(jié)碼加載到內(nèi)存中帝火,這個字節(jié)碼的原始信息存放在classpath目錄下(及Java工程的bin目錄下)的.class文件中,類加載需要將.class文件導(dǎo)入硬盤湃缎,經(jīng)過處理變成字節(jié)碼加載到內(nèi)存犀填。
示例:
public class ClassLoaderTest {
public static void main(String[] args) {
// 輸出ClassLoaderTest的類加載器名稱
System.out.println("ClassLoaderTest類的類加載器名稱:" + ClassLoaderTest.class.getClassLoader().getClass().getName());
System.out.println("System類的類加載器名稱:" + System.class.getClassLoader());
System.out.println("List類的類加載器名稱:" + List.class.getClassLoader());
// 獲取ClassLoaderTest類的類加載器
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
while (null != classLoader) {
System.out.println(classLoader.getClass().getName() + "->");
// 獲取類加載器的父類
classLoader = classLoader.getParent();
}
System.out.println(classLoader);
}
}
打印結(jié)果:
從打印結(jié)果可以看出ClassLoaderTest類是有AppClassLoader加載的。
因?yàn)镾ystem類嗓违,List九巡,Map等這樣的系統(tǒng)提供jar類都在rt.jar中,所以由BootStrap類加載器加載蹂季,因?yàn)锽ootStrap是祖先類冕广,不是Java編寫的疏日,所以打印出class為null。詳情請看第三點(diǎn):類加載器的委托機(jī)制
2). Java虛擬中的類加載器
Java虛擬機(jī)中可以安裝多個類加載器撒汉,系統(tǒng)默認(rèn)的有三個沟优,每個類負(fù)責(zé)加載特定位置的類:BootStrap,ExtClassLoader睬辐,AppClassLoader
由于類加載器本身是Java類挠阁,也需要被類加載器加載,因此必須有一個為類加載器不是Java類溉委,因此Bootstrap是由C/C++代碼編寫鹃唯,被封裝到JVM內(nèi)核中,ExtClassLoader和AppClassLoader是Java類瓣喊。
類加載器屬性結(jié)構(gòu)圖:
3). 類加載器的委托機(jī)制
問題:Java虛擬機(jī)要加載第一個類的時(shí)候坡慌,使用哪一個類加載器加載?
(1). 當(dāng)前線程的類加載器加載線程中的第一個類(當(dāng)前線程的類加載器:Thread類中有一個get/setContextClassLoader(ClassLoader);方法藻三,可以獲取/指定本線程中的類加載器)
(2). 如果類A引用了類B洪橘,Java虛擬機(jī)將使用加載類A的加載器來加載類B
(3). 可以直接調(diào)用ClassLoader.loadClass(String className)方法來指定某個類加載器去加載某個類
每個類加載器加載類時(shí),先委托其上級類加載器來加載棵帽,當(dāng)所有父加載器沒有加載到類時(shí)熄求,回到發(fā)起者類加載器,如果還是加載不了逗概,則會拋出ClassNotFoundException弟晚。
好處:
能夠很好的統(tǒng)一管理類加載,首先交給上級逾苫,如果上級有卿城,就加載,這樣如果之前已經(jīng)加載過的類铅搓,這時(shí)候在來加載它的時(shí)候只要拿過來用就可以了瑟押,無需二次加載
4). 測試ExtClassLoader
- 將ClassLoaderTest類打包為Jar文件,拷貝至
jdk\jre\lib\ext\下
路徑
圖3.png - 接下來在eclipse中運(yùn)行ClassLoaderTest文件星掰,其打印結(jié)果為
圖4.png
打印結(jié)果驗(yàn)證了類加載器的委托機(jī)制.
測試完成后記得將test.jar刪除
5). 類加載器主要方法
- 默認(rèn)的類加載器
System.out.println("默認(rèn)的類加載器:"+ClassLoader.getSystemClassLoader());
- loadClass(String name) 源碼
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
// 加鎖多望,進(jìn)行同步處理,可能多個線程加載類
synchronized (getClassLoadingLock(name)) {
// 檢查是否已經(jīng)加載過
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 如果自定義的類加載器的parent不為null,就調(diào)用parent的loadClass進(jìn)行類加載
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 如果自定義的類加載器的parent為null氢烘,就調(diào)用findBootstrapClassOrNull方法查找類怀偷,就是BootStrap類加載器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 類未發(fā)現(xiàn)異常
}
if (c == null) {
// 如果仍然沒有發(fā)現(xiàn),就調(diào)用自己的findClass方法進(jìn)行進(jìn)行類加載
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;
}
}
-
defineClass播玖,將class文件的字節(jié)數(shù)組編譯成一個class對象椎工,不能重寫,內(nèi)部實(shí)現(xiàn)為C/C++代碼
圖6.png
6). 自定義ClassLoader
- 定義一個被加載的類
/**
* 加載類
* @author mazaiting
*/
public class ClassLoaderAttachment extends Date{
private static final long serialVersionUID = -1892974244318329380L;
@Override
public String toString() {
return "Hello ClassLoader";
}
}
- 自定義ClassLoader
/**
* 自定義類加載器
* 步驟:
* 1. 繼承ClasssLoader
* 2. 重寫findClass(String name) 方法
* @author mazaiting
*/
public class MyClassLoader extends ClassLoader{
/**需要加載類.class文件的目錄*/
private String classDir;
/**無參構(gòu)造*/
public MyClassLoader() {}
public MyClassLoader(String classDir) {
this.classDir = classDir;
}
// 測試,先將ClassLoaderAttachment.class文件加密到工程的class_temp目錄下
public static void main(String[] args) throws Exception {
// 配置運(yùn)行參數(shù)
// ClassLoaderAttachment.class原路徑--E:\test\java_advanced\ClassLoader\bin\com\mazaiting\ClassLoaderAttachment.class
String srcPath = args[0];
// ClassLoaderAttachment.class輸出路徑--E:\test\java_advanced\ClassLoader\class_temp\
String desPath = args[1];
// 獲取文件名
String desFileName = srcPath.substring(srcPath.lastIndexOf("\\") +1);
// 配置目標(biāo)文件路徑
String desPathFile = desPath + "/" + desFileName;
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(desPathFile);
// 將class進(jìn)行加密
encodeAndDecode(fis, fos);
fis.close();
fos.close();
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// class文件的路徑
String classPathFile = classDir + "/" + name + ".class";
try {
// 將class文件進(jìn)行解密
FileInputStream fis = new FileInputStream(classPathFile);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
encodeAndDecode(fis, baos);
byte[] classByte = baos.toByteArray();
// 將字節(jié)流變成一個class
return defineClass(null, classByte, 0, classByte.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
/**
* 加密解密
* @param is 輸入流
* @param os 輸出流
* @throws IOException
*/
private static void encodeAndDecode(InputStream is, OutputStream os) throws IOException {
int len;
while ((len = is.read()) != -1) {
len ^= 0xFF;
os.write(len);
}
}
}
- 運(yùn)行:在MyClassLoader的main方法右鍵 -> Run As -> Run Configurations...晋渺,進(jìn)行如下配置(配置前請確保ClassLoader工程目錄下存在class_temp文件夾,其中第一個參數(shù)是ClassLoaderAttachment.class文件的源路徑脓斩,第二個參數(shù)是加密后存放的目錄)木西,點(diǎn)擊Run運(yùn)行,此時(shí)在
E:\test\java_advanced\ClassLoader\class_temp
將會生成加密后的ClassLoaderAttachment.class
文件
圖7.png - 使用随静,在
ClassLoaderTest
的main函數(shù)中應(yīng)用
public class ClassLoaderTest {
public static void main(String[] args) {
try {
// 加載類
Class classDate = new MyClassLoader("class_temp").loadClass("ClassLoaderAttachment");
// 創(chuàng)建實(shí)例
Date date = (Date) classDate.newInstance();
// 輸出ClassLoaderAttachment類的加載器名稱
System.out.println("ClassLoader: " + date.getClass().getClassLoader().getClass().getName());
// 調(diào)用方法
System.out.println(date.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
打印結(jié)果:
- 如果loadClass方法的參數(shù)為類的全路徑名
// 加載類
Class classDate = new MyClassLoader("class_temp").loadClass("com.mazaiting.ClassLoaderAttachment");
- 將
\class_temp\
路徑下的ClassLoaderAttachment.class復(fù)制到\bin\com\mazaiting\
路徑下八千,然后運(yùn)行
Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value 889275713 in class file com/mazaiting/ClassLoaderAttachment
不合適的魔數(shù)錯誤(class文件的前六個字節(jié)是個魔數(shù)用來標(biāo)識class文件的),因?yàn)榧用芎蟮臄?shù)據(jù)AppClasssLoader無法解析燎猛。