這篇文章咱們關注一個問題:Java程序是怎麼進入JVM並執行的?常常寫Java程序的小夥伴應該都據說過類加載機制,在《深刻理解Java虛擬機》裏周老師已經講的很清楚了,這篇隨筆把以前的筆記以及一些總結從新梳理一下。前面咱們已經知道 .java文件通過編譯後變成Class文件,JVM加載的是字節碼文件。這其中的細節不知道小夥伴們有麼有了解過?java
類從被JVM加載到內存開始,到卸載出內存爲止,整個生命週期包括7個階段,加載、驗證、準備、初始化、卸載這個5個步驟順序是肯定的。數組
- 加載,類的加載時機由JVM自行決定,JVM須要完成3件事情:
- 經過類的全限定名獲取類的二進制字節流
- 將類的靜態存儲結構轉化爲方法區的運行時數據結構
- 在內存中生成類的Class對象,做爲方法區數據的入口
數組在Java中是一種引用類型,數組類的加載由Java虛擬機直接建立。類加載完成後,二進制字節流存儲在方法區中,HotSpot虛擬機會建立一個java.lang.Class對象做爲程序訪問方法區中的數據的外部接口,這個Class對象存儲在方法區中,加載階段何鏈接部分階段是交叉進行的。安全
- 驗證是鏈接部分的第一步,驗證階段是確保Class文件的字節流中包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。由於只有在字節碼層面才能保證Java語言的相對安全性。驗證階段會大體完成4個階段的檢驗動做:
- 文件格式驗證:驗證字節流是否符合Class文件格式的規範,而且能被當前版本的JVM處理。只有驗證經過了,字節流纔會進入方法區存儲。
- 元數據驗證:好比類是否有父類,類是否繼承了不能被繼承的類等,保證不存在不符合Java語言規範的元數據信息。
- 字節碼驗證:對類的方法體進行校驗分析
- 符號引用驗證:對常量池中的各類符號引用進行校驗
- 準備:爲類變量分匹內存並設置類型變量的零值階段。類變量指的是被static修飾的變量,不包括實例變量。例如變量i,在準備階段值就是0L,只有在執行方法的時候值纔會變成1。
private static long a = 1;
- 解析:JVM將常量池內的符號引用替換爲直接引用的過程。這裏要解釋下符號引用何直接引用。
- 符號引用: 用一組符號描述所引用的目標,引用的目標不必定已經加載到內存中。
- 直接引用:直接指向目標的指針、相對偏移量、間接定位到目標的句柄,直接引用的目標已經在內存中。
- 初始化,類加載過程的最後一步,執行類構造器<clinit>()方也就是真正執行類中定義的Java程序代碼。遇到使用new關鍵字實例化對象、讀取或設置一個類的靜態字段、調用一個類的靜態方法這些場景時,若是類沒有被初始化,使用java.lang.reflect包的方法對類進行 反射調用時,那麼必定要先初始化。還有一種場景是初始化子類時,若是父類沒有被初始化,那麼要先初始化父類。再看下接口的初始化,與類不一樣的地方就在於父類的初始化,子接口在使用到父接口的時候才初始化。在多線程環境中,若是多個線程同時初始化一個類,那麼只有線程執行類的<clinit>()方法。
JVM加載字節碼文件靠的是類加載器,這個操做是在JVM外部實現的。這樣應用程序就能夠本身決定如何獲取所需的類。若是兩個類來自同一個Class文件,可是由不一樣的類加載器加載,那麼者兩個類必定是不相等。從JVM角度講,只有兩種加載器。一種是啓動類加載器,是虛擬機自身的一部分,由C++語言實現;還有就是其餘類加載器,由Java語言實現,全都繼承自抽象類java.lang.ClassLoader 獨立於虛擬機外部。數據結構
從開發角度看,主要分爲這三種:多線程
- 啓動類加載器(Bootstrap ClassLoader):加載<JAVA_HOME>/jre/lib目錄中,或者被 -Xbootclasspath參數所指定的路徑中。
- 擴展類加載器(Extension ClassLoader):主要加載<JAVA_HOME>/jre/lib目錄中的
- 應用程序類加載器(Application ClassLoader):加載用戶類路徑(ClassPath)上所指定的類庫。若是咱們沒有自定義過本身的類加載器,那麼這就是程序默認的類加載器。
這些類加載器之間的關係爲:spa
在使用類加載器加載類的過程種,最好遵循雙親委派模型。雙親委派的原理是:類加載器收到加載類的請求時,先把這個請求委派給父類加載去完成,每一層次的加載器按這個這個邏輯執行。那麼全部的加載請求最終都應該傳送到頂層的啓動類加載中。父加載器沒法加載,子加載器纔會本身加載。這樣作的好處是能夠避免類的重複加載,保證程序運行的穩定性。線程
咱們能夠自定義類加載器,總結起來就是:(1)類繼承ClassLoader (2) 重寫findClass() 方法 (3) 調用defineClass()方法。在loadClass()裏若是父類加載失敗,調用findClass()方法加載,這樣仍是符合雙親委派機制的。破壞會雙親委派須要重寫loadClass()方法。指針
參考資料:《深刻理解Java虛擬機》第二版 周志明code
《深刻拆解Java虛擬機》鄭雨迪對象