一、java提供了三種ClassLoader對Class進行載入: 1.BootStrap ClassLoader:稱為啟動類載入器,是Java類載入層次中最頂層的類載入器,負責載入JDK中的核心類庫,如:rt.jar、resources.jar、charsets.jar等,可通過如下程式獲得該類加 ...
一、java提供了三種ClassLoader對Class進行載入:
1.BootStrap ClassLoader:稱為啟動類載入器,是Java類載入層次中最頂層的類載入器,負責載入JDK中的核心類庫,如:rt.jar、resources.jar、charsets.jar等,可通過如下程式獲得該類載入器從哪些地方載入了相關的jar或class文件:
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); for (int i = 0; i < urls.length; i++) { System.out.println(urls[i].toExternalForm()); }
或者
System.out.println(System.getProperty("sun.boot.class.path"));
最後結果為:
/Java/jdk1.6.0_22/jre/lib/resources.jar
/Java/jdk1.6.0_22/jre/lib/rt.jar
/Java/jdk1.6.0_22/jre/lib/sunrsasign.jar
/Java/jdk1.6.0_22/jre/lib/jsse.jar
/Java/jdk1.6.0_22/jre/lib/jce.jar
/Java/jdk1.6.0_22/jre/lib/charsets.jar
/Java/jdk1.6.0_22/jre/classes/
2.Extension ClassLoader:稱為擴展類載入器,負責載入Java的擴展類庫,預設載入JAVA_HOME/jre/lib/ext/目下的所有jar。
3.App ClassLoader:稱為系統類載入器,負責載入應用程式classpath目錄下的所有jar和class文件。
二、ClassLoader的載入原理
1、原理介紹
ClassLoader使用的是雙親委托模型來搜索類的,每個ClassLoader實例都有一個父類載入器的引用(不是繼承的關係,是一個包含的關係),虛擬機內置的類載入器(Bootstrap ClassLoader)本身沒有父類載入器,但可以用作其它ClassLoader實例的的父類載入器。當一個ClassLoader實例需要載入某個類時,它會試圖親自搜索某個類之前,先把這個任務委托給它的父類載入器,這個過程是由上至下依次檢查的,首先由最頂層的類載入器Bootstrap ClassLoader試圖載入,如果沒載入到,則把任務轉交給Extension ClassLoader試圖載入,如果也沒載入到,則轉交給App ClassLoader 進行載入,如果它也沒有載入得到的話,則返回給委托的發起者,由它到指定的文件系統或網路等URL中載入該類。如果它們都沒有載入到這個類時,則拋出ClassNotFoundException異常。否則將這個找到的類生成一個類的定義,並將它載入到記憶體當中,最後返回這個類在記憶體中的Class實例對象。
2、為什麼要使用雙親委托這種模型呢?
因為這樣可以避免重覆載入,當父親已經載入了該類的時候,就沒有必要子ClassLoader再載入一次。考慮到安全因素,我們試想一下,如果不使用這種委托模式,那我們就可以隨時使用自定義的String來動態替代java核心api中定義的類型,這樣會存在非常大的安全隱患,而雙親委托的方式,就可以避免這種情況,因為String已經在啟動時就被引導類載入器(Bootstrcp ClassLoader)載入,所以用戶自定義的ClassLoader永遠也無法載入一個自己寫的String,除非你改變JDK中ClassLoader搜索類的預設演算法。
3、 但是JVM在搜索類的時候,又是如何判定兩個class是相同的呢?
JVM在判定兩個class是否相同時,不僅要判斷兩個類名是否相同,而且要判斷是否由同一個類載入器實例載入的。只有兩者同時滿足的情況下,JVM才認為這兩個class是相同的。就算兩個class是同一份class位元組碼,如果被兩個不同的ClassLoader實例所載入,JVM也會認為它們是兩個不同class。
4、ClassLoader的體系架構:
三、自定義ClassLoader,自定義ClassLoader需要繼承java.lang.ClassLoader或者繼承URLClassLoader
放兩個類型的具體實現代碼:
1.繼承自ClassLoader
public class NetworkClassLoader extends ClassLoader { private String rootUrl; public NetworkClassLoader(String rootUrl) { this.rootUrl = rootUrl; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class clazz = null;//this.findLoadedClass(name); // 父類已載入 //if (clazz == null) { //檢查該類是否已被載入過 byte[] classData = getClassData(name); //根據類的二進位名稱,獲得該class文件的位元組碼數組 if (classData == null) { throw new ClassNotFoundException(); } clazz = defineClass(name, classData, 0, classData.length); //將class的位元組碼數組轉換成Class類的實例 //} return clazz; } private byte[] getClassData(String name) { InputStream is = null; try { String path = classNameToPath(name); URL url = new URL(path); byte[] buff = new byte[1024*4]; int len = -1; is = url.openStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); while((len = is.read(buff)) != -1) { baos.write(buff,0,len); } return baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); } finally { if (is != null) { try { is.close(); } catch(IOException e) { e.printStackTrace(); } } } return null; } private String classNameToPath(String name) { return rootUrl + "/" + name.replace(".", "/") + ".class"; } }
2.繼承自URLClassLoader
ublic class SimpleURLClassLoader extends URLClassLoader { //工程class類所在的路徑 public static String projectClassPath = "E:/IDE/work_place/ZJob-Note/bin/"; //所有的測試的類都在同一個包下 public static String packagePath = "testjvm/testclassloader/"; public SimpleURLClassLoader() { //設置ClassLoader載入的路徑 super(getMyURLs()); } private static URL[] getMyURLs(){ URL url = null; try { url = new File(projectClassPath).toURI().toURL(); } catch (MalformedURLException e) { e.printStackTrace(); } return new URL[] { url }; } public Class load(String name) throws Exception{ return loadClass(name); } public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name,false); } /** * 重寫loadClass,不採用雙親委托機制("java."開頭的類還是會由系統預設ClassLoader載入) */ @Override public Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException { Class clazz = null; //查看HotSwapURLClassLoader實例緩存下,是否已經載入過class clazz = findLoadedClass(name); if (clazz != null ) { if (resolve) { resolveClass(clazz); } return (clazz); } //如果類的包名為"java."開始,則有系統預設載入器AppClassLoader載入 if(name.startsWith("java.")) { try { //得到系統預設的載入cl,即AppClassLoader ClassLoader system = ClassLoader.getSystemClassLoader(); clazz = system.loadClass(name); if (clazz != null) { if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { // Ignore } } return customLoad(name,this); } /** * 自定義載入 * @param name * @param cl * @return * @throws ClassNotFoundException */ public Class customLoad(String name,ClassLoader cl) throws ClassNotFoundException { return customLoad(name, false,cl); } /** * 自定義載入 * @param name * @param resolve * @return * @throws ClassNotFoundException */ public Class customLoad(String name, boolean resolve,ClassLoader cl) throws ClassNotFoundException { //findClass()調用的是URLClassLoader裡面重載了ClassLoader的findClass()方法 Class clazz = ((SimpleURLClassLoader)cl).findClass(name); if (resolve) ((SimpleURLClassLoader)cl).resolveClass(clazz); return clazz; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { return super.findClass(name); } }
四、ClassLoader卸載Class
JVM中的Class只有滿足以下三個條件,才能被GC回收,也就是該Class被卸載(unload):
- 該類所有的實例都已經被GC。
- 載入該類的ClassLoader實例已經被GC。
- 該類的java.lang.Class對象沒有在任何地方被引用。
GC的時機我們是不可控的,那麼同樣的我們對於Class的卸載也是不可控的。
package testjvm.testclassloader; public class TestClassUnLoad { public static void main(String[] args) throws Exception { SimpleURLClassLoader loader = new SimpleURLClassLoader(); // 用自定義的載入器載入A Class clazzA = loader.load("testjvm.testclassloader.A"); Object a = clazzA.newInstance(); // 清除相關引用 a = null; //清除該類的實例 clazzA = null; //清除該class對象的引用 loader = null; //清楚該類的ClassLoader引用 // 執行一次gc垃圾回收 System.gc(); System.out.println("GC over"); } }
參考文檔:
http://blog.csdn.net/xyang81/article/details/7292380
https://my.oschina.net/xianggao/blog/367822