1.簡介:
類加載器負責將.class文件(可能在磁盤上,也可能在網絡上)加載到內存中併爲之生成對應的Java.lang.Class對象。儘管在Java開發中無須過分關心類的加載機制,但是所有的編程人員都應該瞭解其工作機制,明白如何才能讓其更好的滿足我們的需要。類加載器負責加載所有的類,一旦一個類被載入JVM中,同一個類就不會被再次載入了。
JVM中,有一個類加載子系統,它包括了四種類加載器:
Bootstrap ClassLoader:根類加載器,
Extension ClassLoader:擴展類加載器,
System ClassLoader:系統類加載器和類加載器。
根類加載器比較特殊,它並不是Java.lang.ClassLoader的子類,而是由JVM自身類實現的,
擴展類加載器負責加載JRE的擴展目錄(%JAVA_HOME%/jre/lib/ext或者由java.ext.dirs系統屬性指定的目錄)中JAR包的類,通過這種方式,就可以爲java擴展核心類以外的新功能,只要把自己開發的類打包成jar文件,放入JAVA_HOME/jre/lib/ext路徑即可。
系統類加載器,負載在JVM啓動時加載來自Java命令的-classpath選項、java.class.path系統屬性,或CLASSPATH環境變量所指定的JAR包和類路徑。程序可以通過ClassLoader的靜態方法getSystemClassLoader()來獲取系統類加載器。如果沒有特別指定,則用戶自定義的類加載器都以類加載器作爲父加載器。
自定義類加載器通過繼承java.lang.ClassLoader類的方式實現自己的類加載器。
2.類加載機制
JVM的類加載機制主要有以下三種:
(1)全盤負責,就是當一個類加載器負責加載某個class時,該Class所依賴的和引用的其他Class也將由該類加載器負責載入,除非顯式使用另外一個類加載器來載入。
(2)父類委託,先讓父類加載器加載該class,只有在父類加載器無法加載該類時才嘗試從自己的類路徑中加載該類。
(3)緩存機制,它會保證所有加載過的Class都會緩存,當程序中需要使用某個Class時,類加載器先從緩存區中搜尋該Class,只有當緩存區中不存在該Class對象時,系統纔會讀取該類對應的二進制數據,並將其轉換成Class對象,存入緩存區中。這就是爲什麼修改了Class後需要重新啓動JVM,程序所做的修改纔會生效的原因。
注:類加載器之間的父子關係並不是類繼承上的父子關係,這裏的父子關係是類加載器實例之間的關係。
創建自己的類加載器,只需要擴展Java.lang.ClassLoader類,然後覆蓋它的findClass(String
name)方法即可,該方法根據參數指定的類的名字,返回對應的Class對象的引用。
3.自定義類加載器
FileSystemClassLoader.java
package com.www.test;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 自定義文件系統類加載
* @author weiwenwen
*/
public class FileSystemClassLoader extends ClassLoader{
private String rootDir;
public FileSystemClassLoader(String rootDir){
this.rootDir=rootDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException{
Class<?> c=findLoadedClass(name);
//如果已經加載這個類,直接返回加載好的類。如果沒有,加載新的類
if(c!=null){
return c;
}else{
ClassLoader parent=this.getParent();
try {
c=parent.loadClass(name);//委派給父類加載
} catch (Exception e) {
}
if(c!=null){
return c;
}else{
byte[] classData=getClassData(name);//返回字節數組
if(classData==null){
throw new ClassNotFoundException();
}else{
c=defineClass(name, classData, 0, classData.length);
}
}
}
return c;
}
private byte[] getClassData(String classname){
String path=rootDir + "/" + classname.replace('.', '/')+".class";
//將流中的數據轉換爲字節數組
InputStream is=null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
is = new FileInputStream(path);
byte[] buffer =new byte[1024];
int temp=0;
while((temp=is.read(buffer)) != -1){
baos.write(buffer,0,temp);
}
return baos.toByteArray();
} catch (Exception e) {
return null;
} finally{
try{
if(is !=null){
is.close();
}
} catch (IOException e){
e.printStackTrace();
}
try{
if(baos !=null){
baos.close();
}
} catch (IOException e){
e.printStackTrace();
}
}
}
}
Demo.java
package com.www.test;
public class Demo {
public static void main(String[] args) throws Exception{
FileSystemClassLoader loader=new FileSystemClassLoader("D:/weiwenwen/testClassWholeProcess/src/com/www/test");
FileSystemClassLoader loader2=new FileSystemClassLoader("D:/weiwenwen/testClassWholeProcess/src/com/www/test");
Class<?> c = loader.loadClass("com.www.test.HelloWorld");
Class<?> c2 = loader.loadClass("com.www.test.HelloWorld");
Class<?> c3 = loader2.loadClass("com.www.test.HelloWorld");
Class<?> c4 = loader2.loadClass("java.lang.String");
System.out.println(c.hashCode());
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());//同一個類被不同的加載器加載,JVM認爲也是不相同的類
System.out.println(c4.hashCode());
System.out.println(c4.getClassLoader()); //引導類加載器
System.out.println(c3.getClassLoader()); //自定義的類加載器
}
}
運行結果:
1829164700
1829164700
1930164689
2018699554
null
com.www.test.FileSystemClassLoader@631816e9
sun.misc.Launcher$AppClassLoader@73d16e93
4.從上面的程序中可以看到
(1)同一個類被相同的加載器加載被認爲是相同的類,被不同的加載器加載,認爲是不同的類。就像一個項目中有這個類,另一個項目中還有這個類,會被不同的加載器加載。
(2)System,String這些核心類庫屬於根類加載器,根類加載器並沒有繼承ClassLoader抽象類,返回null