Java類載入機制,java載入機制
什麼是類載入器
負責讀取 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選擇將ClassObject Storage Service在方法區中,Java虛擬機器規範並沒有明確要求一定要儲存在方法區或堆區中)
轉換為一個與目標類型對應的java.lang.Class對象;
串連
驗證
驗證階段主要包括四個檢驗過程:檔案格式驗證、中繼資料驗證、位元組碼驗證和符號引用驗證;
準備
為類中的所有靜態變數分配記憶體空間,並為其設定一個初始值(由於還沒有產生對象,執行個體變數將不再此操作範圍內);
解析
將常量池中所有的符號引用轉為直接引用(得到類或者欄位、方法在記憶體中的指標或者位移量,以便直接調用該方法)。這個階段可以在初始化之後再執行。
初始化
- 在串連的準備階段,類變數已賦過一次系統要求的初始值,而在初始化階段,則是根據程式員自己寫的邏輯去初始化類變數和其他資源,舉個例子如下:
public static int value1 = 5; public static int value2 = 6; static{ value2 = 66; }
在準備階段value1和value2都等於0;
在初始化階段value1和value2分別等於5和66;
- 所有類變數初始化語句和靜態代碼塊都會在編譯時間被前端編譯器放在收集器裡頭,存放到一個特殊的方法中,這個方法就是<clinit>方法,即類/介面初始化方法,該方法只能在類載入的過程中由JVM調用;
- 編譯器收集的順序是由語句在源檔案中出現的順序所決定的,靜態語句塊中只能訪問到定義在靜態語句塊之前的變數;
- 如果超類還沒有被初始化,那麼優先對超類初始化,但在<clinit>方法內部不會顯示調用超類的<clinit>方法,由JVM負責保證一個類的<clinit>方法執行之前,它的超類<clinit>方法已經被執行。
- JVM必須確保一個類在初始化的過程中,如果是多線程需要同時初始化它,僅僅只能允許其中一個線程對其執行初始化操作,其餘線程必須等待,只有在活動線程執行完對類的初始化操作之後,才會通知正在等待的其他線程。(所以可以利用靜態內部類實現安全執行緒的單例模式)
- 如果一個類沒有聲明任何的類變數,也沒有靜態代碼塊,那麼可以沒有類<clinit>方法;
何時觸發初始化
自訂類載入器
要建立使用者自己的類載入器,只需要繼承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());//列印類載入成功. }}
JAVA熱部署實現
首先談一下何為熱部署(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/