class裝載驗證流程html
加載java
裝載類的第一個階段 取得類的二進制流 轉爲方法區數據結構 在Java堆中生成對應的java.lang.Class對象
連接mysql
驗證sql
準備數據庫
解析設計模式
初始化數組
執行類構造器<clinit> static變量 賦值語句 static{}語句 子類的<clinit>調用前保證父類的<clinit>被調用 <clinit>是線程安全的
什麼是類裝載器ClassLoader安全
ClassLoader是一個抽象類 ClassLoader的實例將讀入Java字節碼將類裝載到JVM中 ClassLoader能夠定製,知足不一樣的字節碼流獲取方式 ClassLoader負責類裝載過程當中的加載階段
JDK中ClassLoader默認設計模式數據結構
方法app
ClassLoader的重要方法 public Class<?> loadClass(String name) throws ClassNotFoundException 載入並返回一個Class protected final Class<?> defineClass(byte[] b, int off, int len) 定義一個類,不公開調用 protected Class<?> findClass(String name) throws ClassNotFoundException loadClass回調該方法,自定義ClassLoader的推薦作法 protected final Class<?> findLoadedClass(String name) 尋找已經加載的類
分類
BootStrap ClassLoader (啓動ClassLoader) Extension ClassLoader (擴展ClassLoader) App ClassLoader (應用ClassLoader/系統ClassLoader) Custom ClassLoader(自定義ClassLoader) 每一個ClassLoader都有一個Parent做爲父親
協同工做
測試類加載順序
public class FindClassOrder { public static void main(String args[]){ HelloLoader loader=new HelloLoader(); loader.print(); } }
public class HelloLoader { public void print(){ System.out.println("I am in apploader"); } }
直接運行以上代碼: I am in apploader
加上參數 -Xbootclasspath/a:/Users/heliming/IdeaProjects/democloud/jvm/src/main/java
//編譯這個java文件的class文件放入/Users/heliming/IdeaProjects/democloud/jvm/src/main/java目錄 public class HelloLoader { public void print(){ System.out.println("I am in bootloader"); } }
I am in bootloader 此時AppLoader中不會加載HelloLoader I am in apploader 在classpath中卻沒有加載 說明類加載是從上往下的
測試查找類的時候,是從下往上的
強制在apploader中加載
/** * description: https://www.cnblogs.com/cl-rr/p/9081817.html defineClass()方法更多的是用來加載再也不classes下的文件,或者是在AOP時覆蓋原來類的字節碼,須要注意的是,對於同名類使用2次及以上defineClass()回拋出異常。 * * @author: dawn.he QQ: 905845006 * @email: dawn.he@cloudwise.com * @email: 905845006@qq.com * @date: 2019/9/24 6:22 PM */ //package com.zejian.classloader; import java.io.*; import java.lang.reflect.Method; /** * Created by zejian on 2017/6/21. * Blog : http://blog.csdn.net/javazejian [原文地址,請尊重原創] */ public class FileClassLoader extends ClassLoader { private long lastTime; private String rootDir; public FileClassLoader(String rootDir) { this.rootDir = rootDir; } /** * 編寫findClass方法的邏輯 * @param name * @return * @throws ClassNotFoundException */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { // 獲取類的class文件字節數組 byte[] classData = getClassData(name); if (classData == null) { throw new ClassNotFoundException(); } else { //直接生成class對象 return defineClass(name, classData, 0, classData.length); } } /** * 編寫獲取class文件並轉換爲字節碼流的邏輯 * @param className * @return */ 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; } /** * 類文件的徹底路徑 * @param className * @return */ private String classNameToPath(String className) { return rootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class"; } public static void main(String[] args) throws ClassNotFoundException { // String rootDir="/Users/zejian/Downloads/Java8_Action/src/main/java/"; String rootDir="/Users/heliming/IdeaProjects/democloud/jvm/target/classes/"; //建立自定義文件類加載器 FileClassLoader loader = new FileClassLoader(rootDir); try { //加載指定的class文件 // Class<?> object1=loader.loadClass("com.zejian.classloader.DemoObj"); Class<?> object1=loader.loadClass("HelloLoader"); Object o = object1.newInstance(); Method method=o.getClass().getDeclaredMethod("print", null); method.invoke(o, null); //輸出結果:I am in apploader } catch (Exception e) { e.printStackTrace(); } } }
打印:I am in apploader
在查找類的時候,先在底層的Loader查找,是從下往上的。Apploader能找到,就不會去上層加載器加載
替換掉上邊的main函數,測試findClass只能加載一次
public static void main(String[] args) throws ClassNotFoundException { String rootDir = "/Users/heliming/IdeaProjects/democloud/jvm/target/classes/"; //建立自定義文件類加載器 FileClassLoader loader = new FileClassLoader(rootDir); FileClassLoader loader2 = new FileClassLoader(rootDir); try { Class<?> object1 = loader.loadClass("HelloLoader"); Object o = object1.newInstance(); Method method = o.getClass().getDeclaredMethod("print", null); method.invoke(o, null); Class<?> object2 = loader2.loadClass("HelloLoader"); o = object1.newInstance(); method = o.getClass().getDeclaredMethod("print", null); method.invoke(o, null); System.out.println("loadClass->obj1:" + object1.hashCode()); System.out.println("loadClass->obj2:" + object2.hashCode()); //加載指定的class文件,直接調用findClass(),繞過檢測機制,建立不一樣class對象。 Class<?> object3 = loader.findClass("HelloLoader"); //findClass只能加載一次 若是再次加載就會報錯重複加載類 //Class<?> object5 = loader.findClass("HelloLoader"); Class<?> object4 = loader2.findClass("HelloLoader"); System.out.println("loadClass->obj3:" + object3.hashCode()); System.out.println("loadClass->obj4:" + object4.hashCode()); /** * 輸出結果: * loadClass->obj1:644117698 loadClass->obj2:644117698 findClass->obj3:723074861 findClass->obj4:895328852 */ } catch (Exception e) { e.printStackTrace(); } }
雙親委派模式的問題
解決:
Thread. setContextClassLoader() 上下文加載器 是一個角色 用以解決頂層ClassLoader沒法訪問底層ClassLoader的類的問題 基本思想是,在頂層ClassLoader中,傳入底層ClassLoader的實例
從圖可知rt.jar核心包是有Bootstrap類加載器加載的,其內包含SPI核心接口類,因爲SPI中的類常常須要調用外部實現類的方法,而jdbc.jar包含外部實現類(jdbc.jar存在於classpath路徑)沒法經過Bootstrap類加載器加載,所以只能委派線程上下文類加載器把jdbc.jar中的實現類加載到內存以便SPI相關類使用。顯然這種線程上下文類加載器的加載方式破壞了「雙親委派模型」,它在執行過程當中拋棄雙親委派加載鏈模式,使程序能夠逆向使用類加載器,固然這也使得Java類加載器變得更加靈活。爲了進一步證明這種場景,不妨看看DriverManager類的源碼,DriverManager是Java核心rt.jar包中的類,該類用來管理不一樣數據庫的實現驅動即Driver,它們都實現了Java核心包中的java.sql.Driver接口,如mysql驅動包中的com.mysql.jdbc.Driver
,這裏主要看看如何加載外部實現類,在DriverManager初始化時會執行以下代碼
//DriverManager是Java核心包rt.jar的類 public class DriverManager { //省略沒必要要的代碼 static { loadInitialDrivers();//執行該方法 println("JDBC DriverManager initialized"); } //loadInitialDrivers方法 private static void loadInitialDrivers() { sun.misc.Providers() AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { //加載外部的Driver的實現類 ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); //省略沒必要要的代碼...... } }); }
在DriverManager類初始化時執行了loadInitialDrivers()方法,在該方法中經過ServiceLoader.load(Driver.class);
去加載外部實現的驅動類,ServiceLoader類會去讀取mysql的jdbc.jar下META-INF文件的內容,以下所示
而com.mysql.jdbc.Driver繼承類以下:
public class Driver extends com.mysql.cj.jdbc.Driver { public Driver() throws SQLException { super(); } static { System.err.println("Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. " + "The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary."); } }
從註釋能夠看出日常咱們使用com.mysql.jdbc.Driver
已被丟棄了,取而代之的是com.mysql.cj.jdbc.Driver
,也就是說官方再也不建議咱們使用以下代碼註冊mysql驅動
//不建議使用該方式註冊驅動類 Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/cm-storylocker?characterEncoding=UTF-8"; // 經過java庫獲取數據庫鏈接 Connection conn = java.sql.DriverManager.getConnection(url, "root", "root@555");
而是直接去掉註冊步驟,以下便可
String url = "jdbc:mysql://localhost:3306/cm-storylocker?characterEncoding=UTF-8"; // 經過java庫獲取數據庫鏈接 Connection conn = java.sql.DriverManager.getConnection(url, "root", "root@555");
這樣ServiceLoader會幫助咱們處理一切,並最終經過load()方法加載,看看load()方法實現
public static <S> ServiceLoader<S> load(Class<S> service) { //經過線程上下文類加載器加載 ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
很明顯了確實經過線程上下文類加載器加載的,實際上核心包的SPI類對外部實現類的加載都是基於線程上下文類加載器執行的,經過這種方式實現了Java核心代碼內部去調用外部實現類。咱們知道線程上下文類加載器默認狀況下就是AppClassLoader,那爲何不直接經過getSystemClassLoader()獲取類加載器來加載classpath路徑下的類的呢?實際上是可行的,但這種直接使用getSystemClassLoader()方法獲取AppClassLoader加載類有一個缺點,那就是代碼部署到不一樣服務時會出現問題,如把代碼部署到Java Web應用服務或者EJB之類的服務將會出問題,由於這些服務使用的線程上下文類加載器並不是AppClassLoader,而是Java Web應用服自家的類加載器,類加載器不一樣。,因此咱們應用該少用getSystemClassLoader()。總之不一樣的服務使用的可能默認ClassLoader是不一樣的,但使用線程上下文類加載器總能獲取到與當前程序執行相同的ClassLoader,從而避免沒必要要的問題。ok~.關於線程上下文類加載器暫且聊到這,前面闡述的DriverManager類,你們能夠自行看看源碼,相信會有更多的體會,另外關於ServiceLoader本篇並無過多的闡述,畢竟咱們主題是類加載器,但ServiceLoader是個很不錯的解耦機制,你們能夠自行查閱其相關用法。
打破常規模式 and 熱替換
雙親模式的破壞 雙親模式是默認的模式,但不是必須這麼作 Tomcat的WebappClassLoader 就會先加載本身的Class,找不到再委託parent OSGi的ClassLoader造成網狀結構,根據須要自由加載Class
在java目錄下
javac Worker.java 啓動main函數 修改Worker.java 再次javac Worker.java 輸出改變了
HelloMain.java
import java.io.File; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; public class HelloMain { private URLClassLoader classLoader; private Object worker; private long lastTime; // private String classDir="/Users/heliming/IdeaProjects/democloud/jvm/target/classes/"; private String classDir="/Users/heliming/IdeaProjects/democloud/jvm/src/main/java/"; public static void main(String[] args) throws Exception { HelloMain helloMain=new HelloMain(); helloMain.execute(); } private void execute() throws Exception { while(true){ //監測是否須要加載 if(checkIsNeedLoad()){ System.out.println("檢測到新版本,準備從新加載"); reload(); System.out.println("從新加載完成"); } //一秒 invokeMethod(); Thread.sleep(1000); } } private void invokeMethod() throws Exception { //經過反射方式調用 //使用反射的主要緣由是:不想Work被appclassloader加載, // 若是被appclassloader加載的話,再經過自定義加載器加載會有點問題 Method method=worker.getClass().getDeclaredMethod("sayHello", null); method.invoke(worker, null); } private void reload() throws Exception { classLoader = new MyClassLoader(new URL[] { new URL( "file:"+classDir)}); worker = classLoader.loadClass("Worker") .newInstance(); System.out.println(worker.getClass()); } private boolean checkIsNeedLoad() { File file=new File(classDir+ "Worker.class"); long newTime=file.lastModified(); if(lastTime<newTime){ lastTime=newTime; return true; } return false; } }
Worker.java
public class Worker { public void sayHello(){ System.out.println("version:fds"); } }
MyClassLoader.java
import java.net.URL; import java.net.URLClassLoader; public class MyClassLoader extends URLClassLoader { public MyClassLoader(URL[] urls) { super(urls); } // 打破雙親模式,保證本身的類會被本身的classloader加載 @Override protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class c = findLoadedClass(name); if (c == null) { try { //這裏若是是先加載本身沒法找到object類會報錯的因此catch下 c=findClass(name); } catch (Exception e) { } } if(c==null){ c=super.loadClass(name, resolve); } return c; } }