負責讀取 Java 字節代碼,並轉換成java.lang.Class
類的一個實例;
類加載器除了用於加載類外,還可用於確定類在Java虛擬機中的唯一性。
即便是同樣的字節代碼,被不同的類加載器加載之後所得到的類,也是不同的。
通俗一點來講,要判斷兩個類是否“相同”,前提是這兩個類必須被同一個類加載器加載,否則這個兩個類不“相同”。
這裡指的“相同”,包括類的Class對象的equals()
方法、isAssignableFrom()
方法、isInstance()
方法、instanceof
關鍵字等判斷出來的結果。
啟動類加載器,Bootstrap ClassLoader,加載JACA_HOME\lib,或者被-Xbootclasspath參數限定的類
擴展類加載器,Extension ClassLoader,加載\lib\ext,或者被java.ext.dirs系統變量指定的類
應用程序類加載器,Application ClassLoader,加載ClassPath中的類庫
自定義類加載器,通過繼承ClassLoader實現,一般是加載我們的自定義類
類加載器 Java 類如同其它的 Java 類一樣,也是要由類加載器來加載的;除了啟動類加載器,每個類都有其父類加載器(父子關系由組合(不是繼承)來實現);
所謂雙親委派是指每次收到類加載請求時,先將請求委派給父類加載器完成(所有加載請求最終會委派到頂層的Bootstrap ClassLoader加載器中),如果父類加載器無法完成這個加載(該加載器的搜索范圍中沒有找到對應的類),子類嘗試自己加載。
雙親委派好處
類加載分為三個步驟:加載,連接,初始化;
如下圖 , 是一個類從加載到使用及卸載的全部生命周期,圖片來自參考資料;
根據一個類的全限定名(如cn.edu.hdu.test.HelloWorld.class)來讀取此類的二進制字節流到JVM內部;
將字節流所代表的靜態存儲結構轉換為方法區的運行時數據結構(hotspot選擇將Class對象存儲在方法區中,Java虛擬機規范並沒有明確要求一定要存儲在方法區或堆區中)
轉換為一個與目標類型對應的java.lang.Class對象;
驗證
驗證階段主要包括四個檢驗過程:文件格式驗證、元數據驗證、字節碼驗證和符號引用驗證;
准備
為類中的所有靜態變量分配內存空間,並為其設置一個初始值(由於還沒有產生對象,實例變量將不再此操作范圍內);
解析
將常量池中所有的符號引用轉為直接引用(得到類或者字段、方法在內存中的指針或者偏移量,以便直接調用該方法)。這個階段可以在初始化之後再執行。
public static int value1 = 5; public static int value2 = 6; static{ value2 = 66; }
在准備階段value1和value2都等於0;
在初始化階段value1和value2分別等於5和66;
何時觸發初始化
要創建用戶自己的類加載器,只需要繼承java.lang.ClassLoader類,然後覆蓋它的findClass(String name)方法即可,即指明如何獲取類的字節碼流。
如果要符合雙親委派規范,則重寫findClass方法(用戶自定義類加載邏輯);要破壞的話,重寫loadClass方法(雙親委派的具體邏輯實現)。
例子:
package classloader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; class TestClassLoad { @Override public String toString() { return "類加載成功。"; } } public class PathClassLoader extends ClassLoader { private String classPath; public PathClassLoader(String classPath) { this.classPath = classPath; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] classData = getData(name); if (classData == null) { throw new ClassNotFoundException(); } else { return defineClass(name, classData, 0, classData.length); } } private byte[] getData(String className) { String path = classPath + File.separatorChar + className.replace('.', File.separatorChar) + ".class"; try { InputStream is = new FileInputStream(path); ByteArrayOutputStream stream = new ByteArrayOutputStream(); byte[] buffer = new byte[2048]; int num = 0; while ((num = is.read(buffer)) != -1) { stream.write(buffer, 0, num); } return stream.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return null; } public static void main(String args[]) throws ClassNotFoundException, InstantiationException, IllegalAccessException { ClassLoader pcl = new PathClassLoader("D:\\ProgramFiles\\eclipseNew\\workspace\\cp-lib\\bin"); Class c = pcl.loadClass("classloader.TestClassLoad");//注意要包括包名 System.out.println(c.newInstance());//打印類加載成功. } }
首先談一下何為熱部署(hotswap),熱部署是在不重啟 Java 虛擬機的前提下,能自動偵測到 class 文件的變化,更新運行時 class 的行為。Java 類是通過 Java 虛擬機加載的,某個類的 class 文件在被 classloader 加載後,會生成對應的 Class 對象,之後就可以創建該類的實例。默認的虛擬機行為只會在啟動時加載類,如果後期有一個類需要更新的話,單純替換編譯的 class 文件,Java 虛擬機是不會更新正在運行的 class。如果要實現熱部署,最根本的方式是修改虛擬機的源代碼,改變 classloader 的加載行為,使虛擬機能監聽 class 文件的更新,重新加載 class 文件,這樣的行為破壞性很大,為後續的 JVM 升級埋下了一個大坑。
另一種友好的方法是創建自己的 classloader 來加載需要監聽的 class,這樣就能控制類加載的時機,從而實現熱部署。
以上內容主要整理自網絡,加入若干個人理解。
參考資料:
https://www.ibm.com/developerworks/cn/java/j-lo-hotdeploy/
https://askingwindy.gitbooks.io/gitbook-java-interview-note/content/jvm/classloader/classloader.html
https://segmentfault.com/a/1190000004597758
https://www.ibm.com/developerworks/cn/java/j-lo-classloader/