Java類加載機制(二)
類加載器原理
將class文件字節碼內容加載到內存中,并將這些靜態數據轉換為方法區的運行時數據結構,在堆中生成一個代表這個類的java.lang.Class對象,作為方法區類數據訪問的入口
類緩存 標準的JavaSE類記載器可以按照要求查找類,但一旦某個類被加載到類加載器中, 它將維持加載(緩存)一段時間。不過,JVM垃圾回收器可以回收這些Claas對象。
類加載器樹狀結構、雙親委托機制
類加載器樹狀結構
-
引導類加載器 用來加載Java的核心庫(JAVA_HOME/jre/lib/rt/jar,或sun.boot.class.path路徑 下的內容),是用原生的代碼(c++)實現的,并不繼承java.lang.ClassLoader。 加載擴展類加載器和應用程序類加載器。并指定它們的父類加載器。
-
擴展類記載器 用來加載Java的擴展庫(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路徑下的內容)。 Java虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄里面查找并加載Java類。
-
應用程序類加載器 它根據Java應用的類路徑(classpath,java.class.path路類) 一般來說,Java應用的類都是由它來完成加載的。由sun.misc.Launcher$AppClassLoader實現。
-
自定義類加載器 開發人員可以通過繼承java.lang.ClassLoader類的方式實現自己的類加載器,以滿足一些特殊的需要。
public class Demo { public static void main(String[] args) { //獲取應用程序類加載器 System.out.println(ClassLoader.getSystemClassLoader()); //獲取擴展類加載器 System.out.println(ClassLoader.getSystemClassLoader().getParent()); //獲取引導類加載器 System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent()); //獲取classpath System.out.println(System.getProperty("java.class.path")); } }
雙親委托機制
-
代理模式:交給其他類加載器加載指定的類
-
雙親委托機制
(1)就是當某個特定的類加載器接到加載類的請求的時候,首先委托給其父類(父類如果有父類一直向上追溯),直到父類加載器無法加載時,該加載器進行加載。
(2)雙親委托機制是為了保證Java核心庫的類型安全。
這種機制保證不會加載到用戶自定義的java.lang.Class類的情況
(3)類加載器除了用于加載類,也是安全最基本的屏障。
雙親委托機制是代理模式的一種,但是并不是所有的類加載都是雙親委托機制,比如tomcat類加載器首先嘗試特定的 類加載器,加載不到類時在嘗試器父類加載器。
自定義類加載器
如何實現自定義類加載器: (1)繼承java.lang.ClassLoader (2)檢查所請求的類型是否已經被這個類加載器加載到命名空間,如果已經被加載直接返回。 (3)委派給父類加載(也可以不委派,這個程序控制)。 (4)調用自定義加載器findClass()方法獲取字節碼,然后調用defineClass()導入類型到方法區。
public class FileSystemClassLoader extends ClassLoader { 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){ e.printStackTrace(); } 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 { if(is != null){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } if(baos != null){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } } } public class TestMyClassLoader { public static void main(String[] args) throws ClassNotFoundException { FileSystemClassLoader loader = new FileSystemClassLoader("/Users/wjk/Desktop"); FileSystemClassLoader loader1 = new FileSystemClassLoader("/Users/wjk/Desktop"); Class c = loader.loadClass("com.Hello"); Class c1 = loader1.loadClass("com.Hello"); Class c2 = loader.loadClass("com.Hello"); Class c3 = loader.loadClass("java.lang.String"); System.out.println(c.hashCode());//被兩個類加載器加載的同一個類,JVM認為是不同的(c和c1的hashCode值不一樣) System.out.println(c1.hashCode()); System.out.println(c2.hashCode()); System.out.println(c.getClassLoader());//使用的是自定義的類加載器 System.out.println(c3.getClassLoader());//使用的是引導類加載器 } } //結果 1725154839 1670675563 1725154839 classLoaderTest.FileSystemClassLoader@5e2de80c null