類的加載過程
加載
- 通過一個類的全限定名獲取此類的二進制字節(jié)流
- 將這個字節(jié)流所代表的靜態(tài)存儲結構轉化為方法區(qū)的運行時數(shù)據(jù)據(jù)結構
- 在內(nèi)存中生成一個代表這個類的java.lang.Class對象般哼,作為方法區(qū)這個類的各種數(shù)據(jù)訪問入口
加載.class文件的方式
- 從本地系統(tǒng)中直接加載
- 通過網(wǎng)絡獲取
- 從zip壓縮包中讀取,成為日后jar,war格式的基礎
- 運行時計算生成铃在,使用最多的是:動態(tài)代理
- 有其他文件生成(JSP)
- 從數(shù)據(jù)庫中提取
- 從加密文件中獲饶粘(防止反編譯)
鏈接
驗證
- 目的在于確保Class文件的字節(jié)流中包含信息符合當前虛擬機要求,保證被加載類的正確性,不會危害虛擬機自身安全
- 主要包括四種驗證:文件格式驗證唁盏,元數(shù)據(jù)驗證蚕苇,字節(jié)流驗證旗芬,符號引用驗證
準備
- 為類變量分配內(nèi)存并且設置該類變量的默認初始值,即零值
- 這里不包含用final修飾的static捆蜀,因為final在編譯的時候就會分配了疮丛,準備階段會顯式初始化
- 這里不會為實例變量分配初始化,類變量會分配在方法區(qū)辆它,而實例變量是會隨著對象一起分配到Java堆中去
解析
- 將常量池內(nèi)的符號引用轉換為直接引用的過程
- 事實上誊薄,解析操作往往會伴隨著JVM在執(zhí)行完初始化之后再執(zhí)行
- 解析動作主要針對類或接口,字段锰茉,類方法呢蔫,接口方法,方法類型等飒筑。對應常量池中的CONSTANT_Class_info等
初始化
- 初始化階段就是執(zhí)行類構造器方法<clinit>()的過程
- 此方法不需定義片吊,就是javac編譯器自動收集類中的所有類變量的賦值動作和靜態(tài)代碼塊中的語句合并而來
- 構造器方法中指令按語句在源文件中出現(xiàn)的順序執(zhí)行。
public class ClassInitTest {
private static int num = 1;
static {
num = 2;
number = 20;
System.out.println(number);// 會報錯:非法的前向引用
}
private static int number = 10; // 鏈接的準備階段:number=0 -->初始化<clinit>()按順序依次:20 -->10
public static void main(String[] args) {
System.out.println(ClassInitTest.num);// 2
System.out.println(ClassInitTest.number); // 0
}
}
- <clinit>()不同于類的構造器(構造器是虛擬機反編譯后的<init>()方法),當沒有靜態(tài)變量時或靜態(tài)代碼塊协屡,就不會存在
- 若該類具有父類俏脊,JVM會保證父類的<clinit>()執(zhí)行完成后,再執(zhí)行子類的<clinit>()
public class ClassInitTest {
static class Father {
public static int A = 1;
static {
A = 2;
}
}
static class Son extends Father {
public static int B = A
}
public static void main(String[] args) {
System.out.println(Son.B);// 2
}
}
- 虛擬機必須保證一個類的<clinit>()方法在多線程下被同步執(zhí)行
public class ClassInitTest {
public static void main(String[] args) {
Runnable r = () -> {
System.out.println(Thread.currentThread().getName() + "開始");
DeadThread deadThread = new DeadThread();
System.out.println(Thread.currentThread().getName() + "結束");
};
Thread t1 = new Thread(r, "線程1");
Thread t2 = new Thread(r, "線程2");
t1.start();
t2.start();
}
class DeadThread {
static {
if (true) {
System.out.println(Thread.currentThread().getName() + "初始化DeadThread");
while (true) {
}
}
}
}
}
結果是只能輸出一次 ‘初始化DeadThread’肤晓,如果初始化阻塞爷贫,會影響其他類無法使用該類