將class文件字節碼內容加載到內存中,並將這些靜態數據轉換爲方法區的運行時數據結構,在堆中生成一個表明這個類的java.lang.Class對象,做爲方法區類數據訪問的入口
類緩存 標準的JavaSE類記載器能夠按照要求查找類,但一旦某個類被加載到類加載器中, 它將維持加載(緩存)一段時間。不過,JVM垃圾回收器能夠回收這些Claas對象。java
引導類加載器 用來加載Java的核心庫(JAVA_HOME/jre/lib/rt/jar,或sun.boot.class.path路徑下的內容),是用原生的代碼(c++)實現的,並不繼承java.lang.ClassLoader。加載擴展類加載器和應用程序類加載器。並指定它們的父類加載器。mysql
擴展類記載器 用來加載Java的擴展庫(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路徑下的內容)。 Java虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載Java類。c++
應用程序類加載器 它根據Java應用的類路徑(classpath,java.class.path路類) 通常來講,Java應用的類都是由它來完成加載的。由sun.misc.Launcher$AppClassLoader實現。web
自定義類加載器 開發人員能夠經過繼承java.lang.ClassLoader類的方式實現本身的類加載器,以知足一些特殊的須要。sql
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")); } } //結果 sun.misc.Launcher$AppClassLoader@2503dbd3 sun.misc.Launcher$ExtClassLoader@511d50c0 null
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
代理模式:交給其餘類加載器加載指定的類緩存
雙親委託機制 tomcat
(1)就是當某個特定的類加載器接到加載類的請求的時候,首先委託給其父類(父類若是有父類一直向上追溯),直到父類加載器沒法加載時,該加載器進行加載。安全
(2)雙親委託機制是爲了保證Java核心庫的類型安全。服務器
這種機制保證不會加載到用戶自定義的java.lang.Class類的狀況 (3)類加載器除了用於加載類,也是安全最基本的屏障。數據結構
雙親委託機制是代理模式的一種,可是並非全部的類加載都是雙親委託機制,好比tomcat類加載器首先嚐試特定的 類加載器,加載不到類時在嘗試器父類加載器。
如何實現自定義類加載器: (1)繼承java.lang.ClassLoader (2)檢查所請求的類型是否已經被這個類加載器加載到命名空間,若是已經被加載直接返回。 (3)委派給父類加載(也能夠不委派,這個程序控制)。 (4)調用自定義加載器findClass()方法獲取字節碼,而後調用defineClass()導入類型到方法區。
(1)通常狀況下,保證一個類中所關聯的其餘類都是由當前類加載器所加載的。 例如:ClassA自己在擴展類加載器下找到,那麼它裏面new出來的一些也就只能用擴展類加載器查找( 不會低一個級別),因此有的應用程序類加載器能夠找到,卻沒有找到。 (2)JDBC API。它有實現driven的部分(mysql/sql server),咱們的JDBC API都是有引導類加載器 或者擴展類加載器載入的,可是JDBC drive倒是由擴展類記載器或者應用程序類加載器載入,那麼就有可能找不到drive中,在Java領域,其實只要分紅Api+SPI(service provice interface特定廠商提供)的,都會遇到這個問題。簡而言之:接口定義在Java自己,實現卻在第三方,Java本自己使用引導類加載器或者擴展類加載器載入,而第三方實現使用擴展類加載器或者應用程序類記載器加載。
SPI接口是Java核心庫的一部分,是由引導類架子器加載的;SPI實現的Java類通常是由應用程序類加載器加載的。引導類加載器是沒法找到SPI的實現類的,由於它只加載Java的核心庫。
(1)系統類加載器 (2)當前類加載器 (3)當前線程類加載器
線程類加載器是爲了拋棄雙親委託加載鏈式模式。 每個線程都有一個關聯上下文類加載器,若是用new Thread()方式生成 新的線程,新線程將繼續繼承其父線程的上下文類加載器。若是程序對線程上下文 類加載器沒有任何變更的話,程序中全部的線程將都使用系統類加載器(即:應用程序類 加載器)做爲上下文類加載器。
public class Demo3 { public static void main(String[] args) { //得到Demo類的類加載器 ClassLoader loader1 = Demo.class.getClassLoader(); System.out.println(loader1); //得到當前線程類加載器 ClassLoader loader2 = Thread.currentThread().getContextClassLoader(); System.out.println(loader2); //使用自定義類加載器 Thread.currentThread().setContextClassLoader(new FileSystemClassLoader("/Users/wjk/Desktop")); //得到當前線程類加載器 System.out.println(Thread.currentThread().getContextClassLoader()); } } //結果 sun.misc.Launcher$AppClassLoader@2503dbd3 sun.misc.Launcher$AppClassLoader@2503dbd3 classLoaderTest.FileSystemClassLoader@60e53b93
(1)一切爲了安全,TOMCAT不能使用系統默認的類加載器。 緣由: 若是TOMCAT跑你的web項目的時候使用系統默認的類加載器, 你能夠直接肆無忌憚的操做系統的各個目錄,這是至關危險的。對於Java EE容器中的Web應用來講,類加載器的實現 與通常Java應用有所不一樣。每個Web應用都一個對應的類加載器,它先嚐試加載某個類,加載不了再委託給父類加載器, (2)爲了安全TOMCAT須要實現本身的類加載器 我能夠限制你把類寫在指定的位置,不然我不給你加載。
OSGI(Open Service Gateway Initative)是面向Java的動態模塊系統。它爲開發人員提供了 面向服務和基礎組件的運行環境,並提供標準的方式用來管理軟件的生命週期。
Eclipse就是基於OSGI技術構建的。
原理: OSGI中的每一個模塊都包含Java包和類。模塊能夠聲明它所依賴的須要導入的其餘模塊和Java包和 類,也能夠聲明本身導出的包和類,供其餘模塊來使用。也就是說可以隱藏和共享某些Java包和類。 這個是經過OSGI特有的類加載器機制來實現的。OSIG中每一個模塊都有對應一個類加載器,它負責加載模塊 本身包含的Java包和類。當它須要加載Java核心庫(java開頭的包和類)的類時,它要代理給父類加載器來完成。當它須要加載導入的Java類的時候,它會代理給導出此Java類的模塊來完成加載,模塊也能夠顯式聲明某些類和包必須由父類加載器加載。