Java基礎加強之類加載器
學習目標:掌握類加載機制和原理,能夠獨立開發自己的類加載器。
1.類的加載
什么是類加載? 類加載是指將類的class文件讀入內存,并為之創建一個Java.lang.Class對象,也就是說當程序中使用任何類時,系統都會為之建立一個java.lang.Class對象。
類加載器負責加載所有類,系統為所有被載入內存中的類生成一個java.lang.Class實例。一旦一個類被載入JVM中,同一個類不會再被再次載入。
思考問題:怎么樣才算同一個類?
當JVM啟動時,會形成三個類加載器組成的原始類加載器層次結構:
【BootStrap ClassLoader】根類加載器 這是一個特殊的加載器,他并不是有Java編寫,而是JVM自身實現的
【Extension Classloader】擴展類加載器
【System Classloader】系統類加載器
類加載器的父子關系:
實驗獲得類加載器以及了解類加載器的層次結構:
public class ClassloaderDemo{ public static void main(String[] args){ System.out.printlb(ClassLoaderDemo.class.getClassLoader().getName()); System.out.println(System.class.getClassloader()); ClassLoader classloader = ClassLoaderDemo.class.getClassLoader()); while(loader!=null){ System.out.println(loader.getClass().getName()); loader=loader.getParent(); } } |
注意:程序會拋出異常,因為JVM根類加載器不是Java類。
2.類的加載機制,如圖所示:
<1>全盤負責:所謂全盤負責,就是說當一個類加載器負責加載某個Class的時候,該Class所依賴的和引用的其他Class也將由該類加載器負責載入,除非顯式使用另外一個 類加載器來實現載入。
<2>父類委托:意思是先讓父類加載器試圖加載該Class,只有父類加載器無法加載該類是才嘗試從自己的路徑中加載該類。
<3>緩存機制:緩存機制將會保證所有被加載過的Class都會被緩存,當程序中需要使用某個Class時,類加載器先從緩存中搜索該Class,只有當緩存中不存在該Class對象 時,系統才重新讀取該類對應的二進制數據。這就是為什么我們修改Class后,JVM必須重新啟動,修改才生效的原因。
類加載器的父子關系:用戶類加載器—>系統類加載器—>擴展類加載器—>根類加載器
類加載機制:
<1>當JVM需要加載一個類是,到底指派哪個類加載器去加載呢?
首先當前線程的類加載器去加載線程中的第一個類,如果A類中引用了B類,JVM將使用加載A類的加載器來加載B類,最后還可以調用ClassLoader。loadeClass方法指定 某個類加載器去加載某個類。
<2>每個類加載器在加載類時,先委托給其上級加載器。
注意兩點:
當所有的祖宗類加載器都沒有加載到類,回到發起類加載器,還加載不了,那么程序將拋出ClassNotFoundExcetpion,而不是去找發起類加載器的兒子,因為沒有 getChild ()方法,即使有,那么選擇哪一個兒子加載器呢?
面試題:能不能自己寫一個類叫Java.lang.System?
答案:可以寫,但是因為JVM委托機制的存在,會先找到JVM根類加載器,我自己寫也可以,那么我要拋開委托加載機制,我自己指定一個ClassLoader。
3.自定義類加載器
JVM中除了根類加載器之外的所有類加載器都是classloader的子類實例,我們完全可以通過擴展ClassLoader的子類,并重寫ClassLoader所包含的的方法來實現自定義類 加載器,ClassLoader有兩個關鍵的方法:loadClass(),findClass()。
不過我們一般推薦重寫findClass()方法,而不是loadClass()方法,因為重寫findClass()可以避免覆蓋默認類加載器的父類委托,緩存機制兩種策略。
下面是我自己編寫的一個類加載器:
package snippet; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; /** * * @author Administrator *自定義類加載器 */ public class MyClassLoader extends ClassLoader { // 獲取java源文件的二進制碼 public byte[] getBytes(String filename){ File file = new File(filename); InputStream ips=null; byte[] b = new byte[(int) file.length()]; try { ips = new FileInputStream(file); int raw =ips.read(b); if(raw!=file.length()){ throw new IOException("無法完整讀取文件"); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally{ if(ips!=null){ try { ips.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return b; } public boolean compile(String javaFile){ System.out.println("正在編譯"); Process p=null; try { //調用系統javac命令 p=Runtime.getRuntime().exec("javac" + javaFile); try { p.waitFor(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } int ret = p.exitValue(); return ret==0; } @Override protected Class<?> findClass(String name) { Class<?> clazz=null; String fileStub = name.replace(".", "/"); String javaFileName = fileStub + ".java"; String classFileName = fileStub + ".class"; File javaFile = new File(javaFileName); File classFile = new File(classFileName); //如果java源文件存在并且class文件不存在,或者java源文件比class文件修改的時間晚 if(javaFile.exists()&&(!classFile.exists()||javaFile.lastModified()>classFile.lastModified())){ if(!compile(javaFileName)||!classFile.exists()){ try { throw new ClassNotFoundException("未發現class文件"); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //如果class文件已經存在,那么直接生成字節碼 if(classFile.exists()){ byte[] b = getBytes(classFileName); clazz = defineClass(name, b, 0, b.length); } //如果為空,標明加載失敗 if(clazz==null){ try { throw new ClassNotFoundException(name); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return clazz; } } |
上面代碼重寫了findClass方法,通過重寫該方法就可以實現自定義的類加載機制。
學習總結:1.了解了JVM三種類加載器(根類加載器,系統類加載器,擴展類加載器),明白了三種類加載器的作用和范圍
2.學習了JVM三種類加載機制(父類委托,緩存,全盤負責)
3.學習了如何自定義類加載器,通過繼承ClassLoader類,特別要注意兩個關鍵方法:loadClass()和findClass()兩種方法的機制和不同。