背景:聽說classloader類加載機制是進入bat的必經之路。
classloader總述:
普通的java開發其實用到classloader的地方并不多,但是理解透徹classloader類的加載機制,無論是對我們編寫更高效的代碼還是進bat都大有裨益;而從“黃埔軍校”出來的我對classloader的理解都是借鑒了很多書籍和博客,站在了各大博主的肩膀上,感謝你們!上菜,classloader最主要的作用就是將java字節碼文件(后綴為.class)加載到jvm中,jvm在啟動時不會一次性加載所有的class文件,而是根據需要動態加載class文件,畢竟一次性加載太多jar包的class文件jvm吃不消;下面主要研究bootstrap classloader、extention classloader和appclassloader這三種類加載器。
談到classloader就想到我們安裝jdk的時候都會在控制臺輸入java、javac驗證是否安裝成功,而這個javac就是java classloader,測試是否能把java源文件正確編譯成java字節碼文件,下面的截圖就是個javac的小例子,javac之后加載器把java源文件編譯成testclassloader.class字節碼文件。
由于下面要講到classloader的加載路徑,這里順便把java的環境變量也復習一遍。
java_home:
指的是安裝jdk的位置,如:java_home="/library/java/javavirtualmachines/jdk1.8.0_191.jdk/contents/home"
。
path:
配置path(程序的路徑)的作用將就是能夠在命令行窗口直接鍵入程序的名字了,而不再需要鍵入它的全路徑,比如上面代碼中我用的到javac和java兩個命令。如:path=".$path:$java_home/bin"
;就是在java_home路徑上添加了jdk下的bin目錄即可。
classpath:
classpath就是指向jar包的路徑,如:path=".$path:$java_home/bin" ;
"."表示當前目錄。
classloader類加載流程:
三個class loader的執行順序是:bootstrap classloder -> extention classloader -> appclassloader;
1、bootstrap classloder是最頂層的加載類,主要是加載核心類庫,也就是%jre_home%\lib下的rt.jar、resources.jar、charsets.jar和class等資源;并且,可以通過啟動jvm時指定-xbootclasspath和路徑來改變bootstrap classloader的加載目錄,下面有個小荔子。
2、extention classloader是擴展的類加載器,其加載的是目錄%jre_home%\lib\ext目錄下的jar包和class文件;它同樣也可以加載-d java.ext.dirs選項指定的目錄。
3、appclass loader是用于加載當前應用的classpath的所有類,其也稱為systemappclass。
另外有興趣的還可以看下launcher類的源碼,源碼中規定了三個加載器的環境屬性分別為b:sun.boot.class.path、e:java.ext.dirs和a:java.class.path;下面通過代碼來簡單測試寫,如圖:
打印輸出結果:
bootstrapclassloader:
/library/java/javavirtualmachines/jdk1.8.0_191.jdk/contents/home/jre/lib/resources.jar:
/library/java/javavirtualmachines/jdk1.8.0_191.jdk/contents/home/jre/lib/rt.jar:
/library/java/javavirtualmachines/jdk1.8.0_191.jdk/contents/home/jre/lib/sunrsasign.jar:
/library/java/javavirtualmachines/jdk1.8.0_191.jdk/contents/home/jre/lib/jsse.jar:
/library/java/javavirtualmachines/jdk1.8.0_191.jdk/contents/home/jre/lib/jce.jar:
/library/java/javavirtualmachines/jdk1.8.0_191.jdk/contents/home/jre/lib/charsets.jar:
/library/java/javavirtualmachines/jdk1.8.0_191.jdk/contents/home/jre/lib/jfr.jar:
/library/java/javavirtualmachines/jdk1.8.0_191.jdk/contents/home/jre/classesextclassloader:
/users/apple/library/java/extensions:
/library/java/javavirtualmachines/jdk1.8.0_191.jdk/contents/home/jre/lib/ext:
/library/java/extensions:/network/library/java/extensions:
/system/library/java/extensions:/usr/lib/javaappclassloader:
/tjt/eclipse/workspace/tjt/bin:
/library/java/javavirtualmachines/jdk1.8.0_191.jdk/contents/home/jre/lib/rt.jar
為了更好的理解三者之間加載的關系,我們來測試一個類的加載器和它的父類加載以及一些不是我們創建的類如string、double、int等基礎類:
從上圖中可用看出,自己編寫的類test2.class文件是由appclassloader加載的,并且appclassloader有父加載器extclassloader,但extclassloader的父加載器為null;double.class這個java基礎類的加載器為null,其父加載也為空且程序還會報空指針異常錯誤;其實呢,double.class是有bootstrap classloader加載的,也并不是每個加載器都有父加載器;總的來說就是jvm啟動時通過bootstrap類加載器加載rt.jar等核心jar包中的class文件,諸如一些int.class,string.class都是由它加載;jvm初始化sun.misc.launcher并創建extension classloader和appclassloader實例,且將extclassloader設置為appclassloader的父加載器;而bootstrap雖然沒有父加載器,但是它卻可以作為一個classloader的父加載器;另外,一個classloader創建時如果沒有指定parent,那么它的parent默認就是appclassloader;
雙親委托:
當一個類加載器查找class和resource時,是通過“委托模式”進行的,它首先會判斷這個class是不是已經加載成功,如果沒有加載的話它并不是自己進行查找,而是先通過父加載器,然后遞歸下去,直到bootstrap classloader,如果bootstrap classloader找到了,直接返回,如果沒有找到,則一級一級返回,最后是由自身去查找這些對象;這種機制就叫做雙親委托。
從上圖可用看出classloader的加載序列,委托是從下往上,查找過程則是從上向下的,以下有幾個注意事項:
1、一個appclassloader查找資源時,首先會查看緩存是否有,若有則從緩存中獲取,否則委托給父加載器;
2.、重復第一步的遞歸操作,查詢類是否已被加載;
3.、如果extclassloader也沒有加載過,則由bootstrap classloader加載,它首先也會查找緩存,如果沒有找到的話,就去找自己的規定的路徑下,也就是sun.mic.boot.class下面的路徑,找到就返回,找不到就讓子加載器自己去找。
4.、bootstrap classloader如果沒有查找成功,則extclassloader自己在java.ext.dirs路徑中去查找,查找成功就返回,查找不成功則再向下讓子加載器找。
5.、若是extclassloader查找不成功,則由ppclassloader在java.class.path路徑下自己查找查找,找到就返回,如果沒有找到就讓子類找,如果沒有子類則會拋出各種異常。
自定義classloader:
在classloader中有四個很重要實用的方法loadclass()、findloadedclass()、findclass()、defineclass(),可以用來創建屬于自己的類的加載方式;比如我們需要動態加載一些東西,或者從c盤某個特定的文件夾加載一個class文件,又或者從網絡上下載class主內容然后再進行加載等。分三步搞定:
1、編寫一個類繼承classloader抽象類;
2、重寫findclass()方法;
3、在findclass()方法中調用defineclass()方法即可實現自定義classloader;
需求:自定義一個classloader其默認加載路徑為"/tjt/code"下的jar包和資源。首先創建一個test.java,然后javac編譯并把生成的test.class文件放到"/tjt/code"路徑下,然后再編寫一個diskclassloader繼承classloader,最后通過findclassloader的測試類,調用再test.class里面的一個find()方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
package www.baidu; import java.io.bytearrayoutputstream; import java.io.file; import java.io.fileinputstream; import java.io.ioexception; public class diskclassloader extends classloader{ //自定義classloader能將class二進制內容轉換成class對象 private string mypath; public diskclassloader(string path) { mypath = path; } //findclass()方法中定義了查找class的方法 @override protected class <?> findclass(string name) throws classnotfoundexception{ string filename = getfilename(name); file file = new file(mypath,filename); try { fileinputstream is = new fileinputstream(file); bytearrayoutputstream bos = new bytearrayoutputstream(); int len = 0 ; try { while ((len = is.read()) != - 1 ) { bos.write(len); } } catch (ioexception e) { e.printstacktrace(); } byte [] data = bos.tobytearray(); is.close(); bos.close(); //數據通過defineclass()生成了class對象 return defineclass(name, data, 0 ,data.length ); } catch (exception e) { e.printstacktrace(); } return super .findclass(name); } private string getfilename(string name) { int lastindexof = name.lastindexof( '.' ); if (lastindexof == - 1 ) { return name + ".class" ; } else { return name.substring(lastindexof + 1 ) + ".class" ; } } } |
測試結果如下:找到了自定義的加載路徑并且調用了類中的find()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package www.baidu; import java.lang.reflect.method; public class findclassloader { public static void main(string[] args) throws classnotfoundexception { //創建自定義classloader對象 diskclassloader diskl = new diskclassloader( "/tjt/code" ); system.out.println( "classloader is: " +diskl); try { //加載class文件 class clazz = diskl.loadclass( "www.baidu.test" ); if (clazz != null ) { object object = clazz.newinstance(); method declaredmethod = clazz.getdeclaredmethod( "find" , null ); //通過反射調用test類的find()方法 declaredmethod.invoke(object, null ); } } catch (exception e) { e.printstacktrace(); } } } |
總結:
除此之外,classloader還可以進行程序加密(比如你寫了比較騷的jar包),這樣我們就可以在程序中加載特定了類,并且這個類只能被我們自定義的加載器進行加載,提高了程序的安全性,但是用的不多;反正我們在項目上是不允許用classloader加密,寧愿裸奔,了解一下。另外就是tomcat的類加載機制也是遵循雙親委派機制的,并且大部分的加載機制和jvm類加載機制一樣,理解了bootstrap classloader、extention classloader和appclassloader這三種加載器后再看tomcat類的加載就可以橫著走了。
好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:https://www.cnblogs.com/taojietaoge/p/10269844.html