clazz.getClassLoader()
Thread.currentThread().getContextClassLoader();
ClassLoader.getSystemClassLoader()
DriverManager.getCallerClassLoader
類加載器是用於加載類的對象,ClassLoader是一個抽象類。若是咱們給定了一個類的二進制名稱,類加載器應嘗試去定位或生成構成定義類的數據。一種典型的策略是將給定的二進制名稱轉換爲文件名,而後去文件系統中讀取這個文件名所對應的class文件。java
每一個Class對象都會包含一個定義它的ClassLoader的一個引用。數組
數組類的Class對象,不是由類加載器去建立的,而是在Java運行期JVM根據須要自動建立的。對於數組類的類加載器來講,是經過Class.getClassLoader()返回的,與數組當中元素類型的類加載器是同樣的;若是數組當中的元素類型是一個原生類型,數組類是沒有類加載器的【代碼一】。安全
應用實現了ClassLoader的子類是爲了擴展JVM動態加載類的方式。網絡
類加載器典型狀況下時能夠被安全管理器所使用去標識安全域問題。併發
ClassLoader類使用了委託模型來尋找類和資源,ClassLoader的每個實例都會有一個與之關聯的父ClassLoader,當ClassLoader被要求尋找一個類或者資源的時候,ClassLoader實例在自身嘗試尋找類或者資源以前會委託它的父類加載器去完成。虛擬機內建的類加載器,稱之爲啓動類加載器,是沒有父加載器的,可是能夠做爲一個類加載器的父類加載器【雙親委託機制】。jvm
支持併發類加載的類加載器叫作並行類加載器,要求在初始化期間經過ClassLoader.registerAsParallelCapable 方法註冊自身,ClassLoader類默認被註冊爲能夠並行,可是若是它的子類也是並行加載的話須要單獨去註冊子類。ide
在委託模型不是嚴格的層次化的環境下,類加載器須要並行,不然類加載會致使死鎖,由於加載器的鎖在類加載過程當中是一直被持有的。post
一般狀況下,Java虛擬機以平臺相關的形式從本地的文件系統中加載類,好比在UNIX系統,虛擬機從CLASSPATH環境所定義的目錄加載類。
然而,有些類並非來自於文件;它們是從其它來源獲得的,好比網絡,或者是由應用自己構建【動態代理】。定義類(defineClass )方法會將字節數組轉換爲Class的實例,這個新定義類的實例能夠由Class.newInstance建立。測試由類加載器建立的對象的方法和構造方法可能引用其它的類,爲了肯定被引用的類,Java虛擬機會調用最初建立類的類加載器的loadClass方法。ui
二進制名稱:以字符串參數的形式向CalssLoader提供的任意一個類名,必須是一個二進制的名稱,包含如下四種狀況
- "java.lang.String" 正常類
- "javax.swing.JSpinner$DefaultEditor" 內部類
- "java.security.KeyStore\(Builder\)FileBuilder$1" KeyStore的內部類Builder的內部類FileBuilder的第一個匿名內部類
- "java.net.URLClassLoader$3$1" URLClassLoader類的第三個匿名內部類的第一個匿名內部類
代碼一:
public class Test12 { public static void main(String[] args) { String[] strings = new String[6]; System.out.println(strings.getClass().getClassLoader()); // 運行結果:null Test12[] test12s = new Test12[1]; System.out.println(test12s.getClass().getClassLoader()); // 運行結果:sun.misc.Launcher$AppClassLoader@18b4aac2 int[] ints = new int[2]; System.out.println(ints.getClass().getClassLoader()); // 運行結果:null } }
loadClass的源碼以下, loadClass方法加載擁有指定的二進制名稱的Class,默認按照以下順序尋找類:
使用上述步驟若是類被找到且resolve爲true,就會去調用resolveClass(Class)方法
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
findClass的源碼以下,findClass尋找擁有指定二進制名稱的類,JVM鼓勵咱們重寫此方法,須要自定義加載器遵循雙親委託機制,該方法會在檢查完父類加載器以後被loadClass方法調用,默認返回ClassNotFoundException異常。
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
defineClass的源碼以下,defineClass方法將一個字節數組轉換爲Class的實例。
protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) throws ClassFormatError { protectionDomain = preDefineClass(name, protectionDomain); String source = defineClassSourceLocation(protectionDomain); Class<?> c = defineClass1(name, b, off, len, protectionDomain, source); postDefineClass(c, protectionDomain); return c; }
/** * 繼承了ClassLoader,這是一個自定義的類加載器 * @author 夜的那種黑丶 */ public class ClassLoaderTest extends ClassLoader { public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); Class<?> clazz = loader.loadClass("classloader.Test01"); Object object = clazz.newInstance(); System.out.println(object); System.out.println(object.getClass().getClassLoader()); } //------------------------------以上爲測試代碼--------------------------------- /** * 類加載器名稱,標識做用 */ private String classLoaderName; /** * 從磁盤讀物字節碼文件的擴展名 */ private String fileExtension = ".class"; /** * 建立一個類加載器對象,將系統類加載器當作該類加載器的父加載器 * @param classLoaderName 類加載器名稱 */ private ClassLoaderTest(String classLoaderName) { // 將系統類加載器當作該類加載器的父加載器 super(); this.classLoaderName = classLoaderName; } /** * 建立一個類加載器對象,顯示指定該類加載器的父加載器 * 前提是須要有一個類加載器做爲父加載器 * @param parent 父加載器 * @param classLoaderName 類加載器名稱 */ private ClassLoaderTest(ClassLoader parent, String classLoaderName) { // 顯示指定該類加載器的父加載器 super(parent); this.classLoaderName = classLoaderName; } /** * 尋找擁有指定二進制名稱的類,重寫ClassLoader類的同名方法,須要自定義加載器遵循雙親委託機制 * 該方法會在檢查完父類加載器以後被loadClass方法調用 * 默認返回ClassNotFoundException異常 * @param className 類名 * @return Class的實例 * @throws ClassNotFoundException 若是類不能被找到,拋出此異常 */ @Override protected Class<?> findClass(String className) throws ClassNotFoundException { byte[] data = this.loadClassData(className); /* * 經過defineClass方法將字節數組轉換爲Class * defineClass:將一個字節數組轉換爲Class的實例,在使用這個Class以前必需要被解析 */ return this.defineClass(className, data, 0 , data.length); } /** * io操做,根據類名找到對應文件,返回class文件的二進制信息 * @param className 類名 * @return class文件的二進制信息 * @throws ClassNotFoundException 若是類不能被找到,拋出此異常 */ private byte[] loadClassData(String className) throws ClassNotFoundException { InputStream inputStream = null; byte[] data; ByteArrayOutputStream byteArrayOutputStream = null; try { this.classLoaderName = this.classLoaderName.replace(".", "/"); inputStream = new FileInputStream(new File(className + this.fileExtension)); byteArrayOutputStream = new ByteArrayOutputStream(); int ch; while (-1 != (ch = inputStream.read())) { byteArrayOutputStream.write(ch); } data = byteArrayOutputStream.toByteArray(); } catch (Exception e) { throw new ClassNotFoundException(); } finally { try { if (inputStream != null) { inputStream.close(); } if (byteArrayOutputStream != null) { byteArrayOutputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } return data; } }
以上是一段自定義類加載器的代碼,咱們執行這段代碼
classloader.Test01@7f31245a sun.misc.Launcher$AppClassLoader@18b4aac2
能夠看見,這段代碼中進行類加載的類加載器仍是系統類加載器(AppClassLoader)。這是由於jvm的雙親委託機制形成的,private ClassLoaderTest(String classLoaderName)
將系統類加載器當作咱們自定義類加載器的父加載器,jvm的雙親委託機制使自定義類加載器委託系統類加載器完成加載。
改造如下代碼,添加一個path屬性用來指定類加載位置:
public class ClassLoaderTest extends ClassLoader { public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz); Object object = clazz.newInstance(); System.out.println(object); System.out.println(object.getClass().getClassLoader()); } //------------------------------以上爲測試代碼--------------------------------- /** * 從指定路徑加載 */ private String path; ...... /** * io操做,根據類名找到對應文件,返回class文件的二進制信息 * @param className 類名 * @return class文件的二進制信息 * @throws ClassNotFoundException 若是類不能被找到,拋出此異常 */ private byte[] loadClassData(String className) throws ClassNotFoundException { InputStream inputStream = null; byte[] data; ByteArrayOutputStream byteArrayOutputStream = null; className = className.replace(".", "/"); try { this.classLoaderName = this.classLoaderName.replace(".", "/"); inputStream = new FileInputStream(new File(this.path + className + this.fileExtension)); byteArrayOutputStream = new ByteArrayOutputStream(); int ch; while (-1 != (ch = inputStream.read())) { byteArrayOutputStream.write(ch); } data = byteArrayOutputStream.toByteArray(); } catch (Exception e) { throw new ClassNotFoundException(); } finally { try { if (inputStream != null) { inputStream.close(); } if (byteArrayOutputStream != null) { byteArrayOutputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } return data; } public void setPath(String path) { this.path = path; } }
運行一下
class:class classloader.Test01 classloader.Test01@7f31245a sun.misc.Launcher$AppClassLoader@18b4aac2
修改一下測試代碼,並刪除工程下的Test01.class文件
public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/桌面/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz); Object object = clazz.newInstance(); System.out.println(object); System.out.println(object.getClass().getClassLoader()); }
運行一下
class:class classloader.Test01 classloader.Test01@135fbaa4 classloader.ClassLoaderTest@7f31245a
分析
改造後的兩塊代碼,第一塊代碼中加載類的是系統類加載器AppClassLoader,第二塊代碼中加載類的是自定義類加載器ClassLoaderTest。是由於ClassLoaderTest會委託他的父加載器AppClassLoader加載class,第一塊代碼的path直接是工程下,AppClassLoader能夠加載到,而第二塊代碼的path在桌面目錄下,因此AppClassLoader沒法加載到,而後ClassLoaderTest自身嘗試加載併成功加載到。若是第二塊代碼工程目錄下的Test01.class文件沒有被刪除,那麼依然是AppClassLoader加載。
再來測試一塊代碼
public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz.hashCode()); Object object = clazz.newInstance(); System.out.println(object.getClass().getClassLoader()); ClassLoaderTest loader2 = new ClassLoaderTest("loader"); loader2.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/"); Class<?> clazz2 = loader2.loadClass("classloader.Test01"); System.out.println("class:" + clazz2.hashCode()); Object object2 = clazz2.newInstance(); System.out.println(object2.getClass().getClassLoader()); }
結果顯而易見,類由系統類加載器加載,而且clazz和clazz2是相同的。
class:2133927002 sun.misc.Launcher$AppClassLoader@18b4aac2 class:2133927002 sun.misc.Launcher$AppClassLoader@18b4aac2
在改造一下
public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/桌面/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz.hashCode()); Object object = clazz.newInstance(); System.out.println(object.getClass().getClassLoader()); ClassLoaderTest loader2 = new ClassLoaderTest("loader2"); loader2.setPath("/home/fanxuan/桌面/"); Class<?> clazz2 = loader2.loadClass("classloader.Test01"); System.out.println("class:" + clazz2.hashCode()); Object object2 = clazz2.newInstance(); System.out.println(object2.getClass().getClassLoader()); }
運行結果
class:325040804 classloader.ClassLoaderTest@7f31245a class:621009875 classloader.ClassLoaderTest@45ee12a7
ClassLoaderTest是顯而易見,可是clazz和clazz2是不一樣的,這是由於類加載器的命名空間的緣由。
咱們能夠經過設置父類加載器來讓loader和loader2處於同一命名空間
public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/桌面/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz.hashCode()); Object object = clazz.newInstance(); System.out.println(object.getClass().getClassLoader()); ClassLoaderTest loader2 = new ClassLoaderTest(loader, "loader2"); loader2.setPath("/home/fanxuan/桌面/"); Class<?> clazz2 = loader2.loadClass("classloader.Test01"); System.out.println("class:" + clazz2.hashCode()); Object object2 = clazz2.newInstance(); System.out.println(object2.getClass().getClassLoader()); }
運行結果
class:325040804 classloader.ClassLoaderTest@7f31245a class:325040804 classloader.ClassLoaderTest@7f31245a