前面介紹了字節碼的讀法,下面就是把字節碼存入到內存中,那麼他又是怎麼加載的這些字節碼文件的呢?java
虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗解析和初始化,最終造成能夠被虛擬機直接使用的 Java 類型,這就是虛擬機的類加載機制數據結構
咱們從上面的定義能夠看出來他要經歷 加載到內存-->數據校驗-->解析-->初始化多線程
固然上面的過程並不詳細,詳細的過程以下:函數
類的生命週期總共細分爲7個階段
個人全部文章同步更新與Github--Java-Notes,想了解JVM,HashMap源碼分析,spring相關能夠點個star,劍指offer題解(Java版),設計模式。能夠看個人github主頁,天天都在更新喲。
邀請您跟我一同完成 repo
加載過程是第一步,他須要完成三個步驟
加載這個過程的實現多種多樣,虛擬機規範也並無對其進行明確的約束,好比第一句 "經過全限定名獲取二進制流"。沒有規定用設麼方法獲取,從哪裏獲取。
那麼就出現了多種多樣的實現方式
對於一個數組類型和一個非數組類型,後者的可控性在這個階段對於開發人員更強,
由於咱們可使用系統提供的引導類加載器,
也可使用本身定義的類加載器來完成(重寫 loadClass完成),
[]
如int[]
是int,int[][]
是int)是由類加載器建立int[][]
是int[]
規則以下:
int[]
的組件類型是基本類型)
加載過程可能和驗證階段的一部分交叉進行,可是兩個的開始時間還是保持固定的先後順序
這個過程不必定非要進行,若是已經反覆驗證過,實施階段能夠經過 -Xverify:none 來關閉來進行優化。
這個階段主要是保護虛擬機不會由於載入有害的字節流而崩潰
主要完成下列四個階段的驗證:
文件格式驗證
0xCAFEBABE
開頭只有經過了這個階段,字節碼才載入到內存中,後面的3個驗證再也不對字節碼進行操做,直接操做方法區的存儲結構
元數據驗證
字節碼驗證
主要是確保被校驗的方法在運行時不會作出危害虛擬機的事件
符號引用驗證
將符號引用轉化爲直接引用,這個轉化動做將發生在解析階段。主要校驗下列內容
若是不能經過驗證,會拋出java.lang.IncompatibleClassChangeError
異常類型的子類,如
準備階段是正式爲類變量分配內存並設置初始值的階段,這些變量所使用的內存在方法區中分配
這裏有兩個注意點;
這裏是類變量(被static修飾的變量)不是實例變量
設置初始值並不等於初始化,只是將其在內存中的值設置爲"零值"
例如 public static int value = 123;
準備階段完成以後是value 是0而不是123;
由於這個時候還沒有執行任何Java方法,而把value賦值爲123的
putstatic
指令是程序被編譯後,存放於類構造器<clinit>()
方法中,因此這個會在初始化階段執行。初始化階段會講到
若是是被final修飾的類變量,那麼準備階段完成以後他就是123
public static final int value = 123
;由於被final修飾的值是存放在 字節碼文件的 ConstantValue屬性表中,若是不瞭解這個能夠看看個人這篇文章,能看懂的字節碼
各個數據類型的初始值以下表
解析是虛擬機將常量池內的符號引用轉換爲直接引用的過程
初始化相對來講比較重要,由於他是類加載的最後一步,也是開始真正執行類中定義的Java代碼,前面的步驟除了能夠本身定義類加載器以外都是由虛擬機主導或者控制的
主動引用:
new
、getstatic
、putstatic
、invokestatic
這四條命令的時候,若是類沒有進行初始化,則進行初始化java.lang.reflect
包的方法對類進行反射調用的時候,若是沒有,則初始化java.lang.invoke.MethodHandle
實例最後的解析結果REF_getStatic
、REF_putStatic
、REF_invokeStatic
的方法句柄,而且這個方法句柄所對應的類沒有進行初始化,則先觸發初始化這5類場景被稱爲主動引用,除此以外,全部引用類的方法都不會觸發初始化,成爲被動引用
《深刻理解Java虛擬機》這本書,這段話放在了前面,不過我以爲這個應該放在後面,由於沒有以前的準備階段,咱們並不知道初始化階段是執行類構造器
<clinit>()
。因此他舉得例子有些就看不懂,爲啥要用static代碼塊
<clinit>()
包含的內容、執行順序等等,這些也須要先了解
static{}
)<init>()
方法)不一樣,他不須要顯示的調用父類構造器,虛擬機會保證子類構造器方法執行前,父類構造器先執行。
<clinit>()
方法對於類或接口來講並非必須的,若是一個類中沒有對變量的賦值操做,那麼編譯器能夠不爲這個類生成<clinit>()
方法clinit
方法不須要先執行父類接口的<clinit>
方法。只有當父接口中定義的變量使用時,父接口才被初始化(對比主動引用的第三條)<clinit>()
方法<clinit>()
會被正確的加鎖、同步,<clinit>()
方法,其餘線程會被阻塞執行順序
package classInit;
/** * 由於父類初始化必定要在子類的<clinit>()方法前,因此輸出 2,參考執行順序的第三條 */
public class ClinitTest {
static class Parent{
public static int A = 1;
static {
A = 2;
}
}
static class Sub extends Parent{
public static int B = A;
}
public static void main(String[] args) {
System.out.println(Sub.B);
}
}
複製代碼
多線程阻塞
package classInit;
public class DeadLockClass {
static class DeadLoopClass {
static {
if (true) {
System.out.println(Thread.currentThread() + "init DeadLockClass");
while (true) {
}
}
}
}
public static void main(String[] args) {
Runnable script = () -> {
System.out.println(Thread.currentThread()+"start");
DeadLoopClass dlc = new DeadLoopClass();
System.out.println(Thread.currentThread()+"end");
};
Thread thread1 = new Thread(script);
Thread thread2 = new Thread(script);
thread1.start();
thread2.start();
}
}
複製代碼
由執行結果可知,線程1已經進入阻塞;
被動引用的例子1(我把類所有放在一個代碼塊中)
package classInit.example1;
public class SuperClass {
static {
System.out.println("superClassInit");
}
public static int value = 123;
}
public class SubClass extends SuperClass{
static {
System.out.println("SubClassInit");
}
}
/** * 不會觸發子類的初始化,由於他並無在那5個狀況中 * 子類引用父類的靜態字段,不會致使子類觸發 */
public class ClassInit {
public static void main(String[] args) {
System.out.println(SubClass.value);
}
}
複製代碼
並無觸發子類的初始化,由於子類並不符合上述5中條件
package classInit.example2;
import classInit.example1.SuperClass;
/** * 經過數組定義來引用類,不會觸發此類的初始化 */
public class NotInit {
public static void main(String[] args) {
SuperClass[] superClasses = new SuperClass[10];
}
}
複製代碼
package classInit.example3;
public class ConstClass {
static {
System.out.println("ConstClass init");
}
public static final String HELLOWORLD = "hello world";
}
/** * 沒有輸出 "ConstClass init", * 由於常量在編譯階段經過常量傳播優化,已經將常量放進了 NotInit 類的常量池中 * 之後NotInit對常量的引用實際都被轉化爲 NotInit 對自身常量池的引用 * * 也就是說實際上 NotInit 的class文件中並無ConstClass的符號引用入口 * 這兩個類在編譯成Class文件以後就已經不存在聯繫了 */
public class NotInit {
public static void main(String[] args) {
System.out.println(ConstClass.HELLOWORLD);
}
}
複製代碼
至此,類加載的過程已經所有結束