在馮諾依曼的計算機模型中,任何程序都要加載到內存才能與CPU進行交流。字節碼.class文件一樣須要加載到內存中,才能夠實例化。java
其中,類加載器ClassLoader的使命就是加載.class文件到內存中。數據庫
在加載類時,使用的是Parents Delegation Model,即雙親委派模型。bootstrap
Java類加載器是一個運行時核心基礎設施模塊,以下圖,主要是在啓動之初進行類的加載(Load)、連接(Link)、初始化(Init)。網絡
第一步,Load階段。數據結構
讀取.class文件流,並轉換爲特定的數據結構,初步校驗cafe babe魔法數、常量池、文件長度、是否有父類等,而後建立對應類的java.lang.Class實例。框架
第二步,Link階段。ide
包括驗證、準備和解析三個步驟。佈局
第三步,Init階段。this
執行類構造器的<clinit>方法。加密
類加載器
類加載器相似於原始部落結構,存在權利等級制度。
最高一層是Bootstrap ClassLoader,它是在JVM啓動時建立的,是最根基的類加載器,負責裝載最核心的Java類,好比Object、System、String等。由C++編寫。
第二層是Extension ClassLoader(JDK9以後變爲了Platform ClassLoader,平臺類加載器)。用以加載擴展的系統類,好比XML、加密、壓縮等相關類。Java編寫。
第三層是Application ClassLoader,應用類加載器,主要是加載用戶自定義classpath下的類。
雙親委派模型
結合上圖,咱們來看一下ClassLoader的loadClass()方法:
1 /** 2 @Param resolve 是否連接 3 @Param name 類名 4 */ 5 protected Class<?> loadClass(String name, boolean resolve) 6 throws ClassNotFoundException 7 { 8 synchronized (getClassLoadingLock(name)) { 9 // 首先檢查該類是否已經被加載過 10 Class<?> c = findLoadedClass(name); 11 if (c == null) { 12 long t0 = System.nanoTime(); 13 try { 14 // 若是父加載器不爲空,就用它的父加載器去加載該類 15 if (parent != null) { 16 c = parent.loadClass(name, false); 17 } else { 18 // 到這裏的話就是bootstrapClassLoader 19 // 這是一個本地方法,使用bootstrapClassLoader去加載該類 20 // 若是加載不了,就返回空 21 c = findBootstrapClassOrNull(name); 22 } 23 } catch (ClassNotFoundException e) { 24 25 } 26 27 // 若是返回空了,證實其父類加載器不能加載該類 28 if (c == null) { 29 30 long t1 = System.nanoTime(); 31 // 使用本身進行加載 32 c = findClass(name); 33 34 // this is the defining class loader; record the stats 35 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); 36 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 37 sun.misc.PerfCounter.getFindClasses().increment(); 38 } 39 } 40 // 若是resove爲true,就連接 41 if (resolve) { 42 resolveClass(c); 43 } 44 return c; 45 } 46 }
低層次的當前類加載器,不能覆蓋更高層次類加載器已經加載過的類。
若是底層的類加載器想要加載一個未知類,要很是有禮貌的向上級詢問:」請問,這個類已經加載了嗎?「,被詢問的高層類首先要回答本身一個問題:我是否加載過此類?若是答案爲否,那麼又向上詢問,直到Bootstrap ClassLoader爲止,若是它也未加載過而且並加載,就會逐層交給下一層類加載器,反應在源碼中就是第28行逐層遞歸退出。
咱們看一下Bootstrap全部已經加載的類庫:
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
Arrays.asList(urls).forEach(System.out::println);
結果:
file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/resources.jar file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/rt.jar file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/sunrsasign.jar file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/jsse.jar file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/jce.jar file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/charsets.jar file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/jfr.jar file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/classes
Bootstrap的加載路徑是能夠追加的,不建議修改或刪除原有的加載路徑。經過-Xbootclasspath/a:參數能夠增長類加載路徑。
若是想在啓動時觀察加載了哪一個jar包中的類,能夠增長-XX:+TraceClassLoading參數,此參數在解決類衝突時很是實用。
何時須要自定義類加載器?
(1)隔離加載類。在某些框架內進行中間件與應用的模塊隔離,把類加載到不一樣的環境。
(2)修改類加載方式。出了Bootstrap外,其餘的加載並不是都要引入。
(3)擴展加載源。好比從數據庫、網絡進行加載。
(4)防止源碼泄露。Java代碼容易被編譯和篡改,能夠進行編譯加密。那麼類加載器也須要自定義。
實現自定義類加載器的步驟:繼承ClassLoader,重寫findClass()方法,調用defineClass()方法。下面是一個例子:
public class MyClassLoader extends ClassLoader { private String path; private String name; public MyClassLoader(String path, String name) { this.path = path; this.name = name; } @Override public Class<?> findClass(String name) throws ClassNotFoundException { byte[] data = null; try { data = getClassBytes(name); return defineClass(name, data, 0, data.length); } catch (IOException e) { e.printStackTrace(); } return null; }
// 獲取字節碼流 private byte[] getClassBytes(String className) throws IOException { InputStream in = null; ByteArrayOutputStream baos = null; try { int len = 0; byte[] bytes = new byte[1024]; in = new FileInputStream(new File(path + className + ".class")); baos = new ByteArrayOutputStream(); while ((len = in.read(bytes)) != -1) { baos.write(bytes, 0, len); baos.flush(); } } catch (Exception e) { }finally { baos.close(); in.close(); } return baos.toByteArray(); } } public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { MyClassLoader classLoader = new MyClassLoader("E:\\","my classloader"); Class clz = classLoader.findClass("Dog"); Field[] fields = clz.getDeclaredFields(); System.out.println(Arrays.toString(fields)); }