類加載器的作用
- 通過一個類的全限定名稱來獲取此類的二進制字節(jié)流,并加載到內(nèi)存中(需要使用類加載器)
- 將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)
- 在堆中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口
類緩存
標準的JavaSE類加載器可以按要求查找類,但一旦某個類被加載到類加載器中,它將維持加載(緩存)一段時間。不過,JVM垃圾收集器可以回收這些Class對象
類加載器的層級結(jié)構(gòu)(樹狀結(jié)構(gòu))
1.引導類加載器(Bootstrap ClassLoader)
它用來加載Java的核心庫(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.Path路徑下的內(nèi)容),是用原生代碼來實現(xiàn)的,并不繼承自java.lang.classloader。
加載擴展類和應用程序類加載器,并指定他們的父類加載器。
啟動類加載器無法被Java程序直接引用
2.擴展類加載器(Extension ClassLoader)
- 用來加載Java的擴展庫(JAVA_HOME/jre/ext/*.jar或java.ext.dirs路徑下的內(nèi)容)。 Java虛擬機的實現(xiàn)會提供一個擴展庫目錄。該類加載器在此目錄里面查找并加載Java類。
- 由sun.misc.Launcher$ExtClassLoader實現(xiàn)。
**3.應用程序類加載器(Application ClassLoader) **
- 它根據(jù)Java應用的類路徑(classpath,java.class.path類。 一般來說,Java應用的類都是由它來完成加載的。
- 由sun.misc.Launcher$AppClassLoader實現(xiàn)。
4.自定義類加載器
- 開發(fā)人員可以用過繼承java.lang.ClassLoader類的方式實現(xiàn)自己的類加載器,以滿足一些特殊的要求
雙親委派模式
雙親委派模型
從JDK1.2開始,java虛擬機規(guī)范推薦開發(fā)者使用雙親委派模式(ParentsDelegation Model)進行類加載,其加載過程如下:
- 如果一個類加載器收到了類加載請求,它首先不會自己去嘗試加載這個類,而是把類加載請求委派給父類加載器去完成。
- 每一層的類加載器都把類加載請求委派給父類加載器,直到所有的類加載請求都應該傳遞給頂層的啟動類加載器。
- 如果頂層的啟動類加載器無法完成加載請求,子類加載器嘗試去加載,如果連最初發(fā)起類加載請求的類加載器也無法完成加載請求時,將會拋出ClassNotFoundException,而不再調(diào)用其子類加載器去進行類加載。
雙親委派 模式的類加載機制的優(yōu)點是java類它的類加載器一起具備了一種帶優(yōu)先級的層次關(guān)系,越是基礎(chǔ)的類,越是被上層的類加載器進行加載,保證了java程序的穩(wěn)定運行。
注意:
- 并不是所有的類記載其都采用雙親委托機制
- tomcat服務(wù)器類加載器也是用代理模式,所不同的是它首先嘗試去加載某個類,如果找不到再找代理給父類加載器。這與一般類加載器的順序是相反的。
我們可以簡單地自定義一個類加載器,用于加載某個class
public class FileSystemClassLoader extends ClassLoader {
//文件的根目錄
private String rootDir;
public FileSystemClassLoader(String rootDir){
this.rootDir=rootDir;
}
//重寫findClass方法
@Override
protected Class<?> findClass(String s) throws ClassNotFoundException {
Class c=findLoadedClass(s);
if (c!=null){
return c;
}else {
ClassLoader parent=this.getParent();
//parent獲取不到class時會拋出異常,為了繼續(xù)執(zhí)行使用try catch包裹
try{
c=parent.loadClass(s);
}catch (Exception e){
}
if (c!=null){
return c;
}else {
byte[] classData=getClassData(s);
if (classData==null){
throw new ClassNotFoundException();
}else {
//將字節(jié)數(shù)組轉(zhuǎn)為Class
c=defineClass(s,classData,0,classData.length);
}
}
}
return c;
}
//將文件轉(zhuǎn)為字節(jié)數(shù)組
private byte[] getClassData(String className) {
//改為文件地址
String path=rootDir+"/"+className.replace(".","/")+".class";
System.out.println(path);
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
InputStream inputStream=null;
try {
inputStream=new FileInputStream(path);
byte[] buffer=new byte[1024];
int temp=0;
while ((temp=inputStream.read(buffer))!=-1){
byteArrayOutputStream.write(buffer,0,temp);
}
return byteArrayOutputStream.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
if (inputStream!=null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (byteArrayOutputStream!=null){
try {
byteArrayOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
使用
public class UseCustomClassLoader {
public static void main(String[]args){
FileSystemClassLoader loader=new FileSystemClassLoader("/home/xjk");
FileSystemClassLoader loader2=new FileSystemClassLoader("/home/xjk");
try {
Class clazz1=loader.findClass("com.jk.bean.Emp");//本項目自定義的類調(diào)用AppClassLoader
System.out.println(clazz1.getClassLoader());
Class clazz2=loader.findClass("java.lang.String");//rt.jar里的類調(diào)用BootstrapClassLoader
System.out.println(clazz2.getClassLoader());
Class clazz3=loader.findClass("com.company.Main");//項目外的類調(diào)用自定義的FileSystemClassLoader
System.out.println(clazz3.getClassLoader());
Class clazz4=loader2.findClass("com.company.Main");//使用不同類加載器,Class對象不一致
System.out.println(clazz4.getClassLoader());
System.out.println(clazz3==clazz4);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
輸出結(jié)果
sun.misc.Launcher$AppClassLoader@18b4aac2
null
com.jk.jvm.FileSystemClassLoader@1d44bcfa
com.jk.jvm.FileSystemClassLoader@6f94fa3e
false
因為BootstrapClassLoader無法被Java程序直接引用,所以顯示為空