最近被 一句話所觸動——**種一棵樹最好的時間是十年前,其次是如今。**因此決定要開始記錄本身的學習之路。html
咱們都知道,每一個.java文件能夠通過javac指令編譯成.class文件,裏面包含着java虛擬機的機器指令。當咱們須要使用一個java類時,虛擬機會加載它的.class文件,建立對應的java對象。將.class調入虛擬機的過程,稱之爲加載。java
loading :加載。經過類的徹底限定名找到.class字節碼文件,同時建立一個對象。mysql
verification:驗證。確保class字節碼文件符合當前虛擬機的要求。c++
preparation:準備。這時候將static修飾的變量進行內存分配,同時設置初始值。sql
resolution:解析。虛擬機將常量池中的符號引用變爲直接引用。數據庫
initialization:初始化。類加載的最後階段。若是這個類有超類,進行超類的初始化,執行類的靜態代碼塊,同時給類的靜態變量賦予初值。前面的preparation階段是分配內存,都只是默認的值,並無被賦予初值。數組
類加載器的任務是將類的二進制字節流讀入到JVM中,而後變成一個JVM能識別的class對象,同時實例化。因而咱們就能夠分解ClassLoader的任務。ide
咱們查看源碼,找到對應解決方案:post
在ClassLoader中,定義了兩個接口:學習
findClass用於找到二進制文件並讀入,調用defineClass用字節流變成JVM能識別的class對象,同時實例化class對象。
咱們來看個例子:
protected Class<?> findClass(String name) throws ClassNotFoundException { // 獲取類的字節數組,經過name找到類,若是你對字節碼加密,須要本身解密出來 byte[] classData = getClassData(name); if (classData == null) { throw new ClassNotFoundException(); } else { //使用defineClass生成class對象 return defineClass(name, classData, 0, classData.length); } }
提到類加載器,必定得涉及的是委派模式。在JAVA中,ClassLoader存在一下幾類,他們的關係以下:
Bootstrap ClassLoader:引導類加載器。採用原生c++實現,用於加載java的核心類(%JAVA_HOME%/lib
路徑下的核心類庫或者 -Xbootclasspath
指定下的jar包)到內存中。沒有父類。
Extension ClassLoader:擴展類加載器。java實現,加載/lib/ext
目錄下或者由系統變量-Djava.ext.dir指定位路徑中的類庫。父類加載器爲null。
System ClassLoader:它會根據java應用的類路徑(CLASSPATH)來加載類,即java -classpath
或-D java.class.path
指定路徑下的類庫,也就是咱們常常用到的classpath路徑。通常來講,java應用的類都是由它完成。能夠由ClassLoader.getSystemLoader()方法得到。父類加載器是Extension ClassLoader。
Customize ClassLoader:用戶自定義加載器。用於完成用戶自身的特有需求。父類加載器爲System ClassLoader。
而代理模式 ,就是像上面圖片所展現那樣,當要加載一個類時,加載器會尋求其父類的幫助,讓父類嘗試去加載這個類。只有當父類失敗後,纔會由本身加載。ClassLoader的loadClass()方法中體現。
示例代碼:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { 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 // 若是都沒有找到,則經過自定義實現的findClass去查找並加載 c = findClass(name); } } if (resolve) {//是否須要在加載時進行解析 resolveClass(c); } return c; } }
findClass(String name)的相似實現上面有示例,resolveClass()就是完成解析功能。
URLClassLoader是經常使用的ClassLoader類,其實現了findclass()接口,因此若是自定義時繼承URLClassLoader能夠不用重寫findclass()。ExtClassLoader在代理模式中屬於Extension ClassLoader,而AppClassLoader屬於System ClassLoader。
前面咱們提到過BootStrap ClassLoader加載的是%JAVA_HOME%/lib下的核心庫文件,而CLASSPATH路徑下的庫由System ClassLoader加載。但在java語言中,存在這種現象,Java 提供了不少服務提供者接口(Service Provider Interface,SPI),容許第三方爲這些接口提供實現,如JDBC、JCE、JNDI等。而SPI是Java的核心庫文件,由 BootStrap ClassLoader加載,第三方實現是放在CLASSPATH路徑下,由System ClassLoader加載。當BootStrap ClassLoader啓用時,須要加載其實現,但本身找不到,又由於代理模式的存在,沒法委託System ClassLoader來加載,因此沒法實現。
Contex ClassLoader(線程上下文加載器)恰好能夠解決這個問題。
Contex ClassLoader(線程上下文加載器)是從 JDK 1.2 開始引入的。類 java.lang.Thread
中的方法 getContextClassLoader()
和 setContextClassLoader(ClassLoader cl)
用來獲取和設置線程的上下文類加載器。若是沒有經過 setContextClassLoader(ClassLoader cl)
方法進行設置的話,線程將繼承其父線程的上下文類加載器。Java 應用運行的初始線程的上下文類加載器是系統類加載器。在線程中運行的代碼能夠經過此類加載器來加載類和資源。
例子JDBC:
public class DriverManager { //省略...... static { loadInitialDrivers(); println("JDBC DriverManager initialized"); } private static void loadInitialDrivers() { sun.misc.Providers() AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); //省略沒必要要的代碼...... } }); } public class Driver extends com.mysql.cj.jdbc.Driver { public Driver() throws SQLException { super(); } static { //省略 } } public static <S> ServiceLoader<S> load(Class<S> service) { //經過線程上下文類加載器加載 ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); } //調用 String url = "jdbc:mysql://localhost:3342/cm-storylocker?characterEncoding=UTF-8"; // 經過java庫獲取數據庫鏈接 Connection conn = java.sql.DriverManager.getConnection(url, "root", "password");
咱們能夠看到,當咱們在使用JDBC的時候,會使用有DriverManager類,它的static代碼區引用的ServiceLoader類會完成JDBC實現類的加載。
給出例子,重寫文件系統加載器
public class FileSystemClassLoader extends ClassLoader { private String rootDir; public FileSystemClassLoader(String rootDir) { this.rootDir = rootDir; } protected Class<?> findClass(String name) throws ClassNotFoundException { //根據規則獲取字節流數組 byte[] classData = getClassData(name); if (classData == null) { throw new ClassNotFoundException(); } else { return defineClass(name, classData, 0, classData.length); } } //設定本身的讀取規則 private byte[] getClassData(String className) { String path = classNameToPath(className); try { InputStream ins = new FileInputStream(path); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = ins.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return null; } private String classNameToPath(String className) { return rootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class"; } }
本文學習參考自
[1] https://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html#minor1.1
[2] http://www.javashuo.com/article/p-zoasinon-cr.html
[3] https://juejin.im/post/5e1aaf626fb9a0301d11ac8e#heading-8