加載的全過程,分爲三步:加載
、鏈接
(驗證、準備、解析)、初始化
、(使用、卸載)
加載
字節碼的來源:
- 硬盤上的class文件
- 網絡上的字節碼(服務器、其他程序發來的)
- jar包、zip文件
- 數據庫中的class文件
字節碼被類加載器進行加載,將class文件字節碼加載到內存中。加載完之後,會在方法區形成運行時數據結構。同時,在堆中生成一個代表這個類的java.lang.Class對象,作爲方法區類數據的訪問入口
鏈接
將Java類的二進制代碼合併到JVM的運行狀態之中
- 驗證:確保加載的類信息符合JVM狀態,沒有安全方面的問題
- 準備:正式爲變量(static變量)分配內存,並設置類變量初始值的傑頓,這些內存走在方法區中進行分配
- 解析:虛擬機常量池內的
符號引用
替換爲直接引用
的過程
類名稱、變量名稱、字符串,都是作爲常量存在的。加載成字節碼之後,每一個類都有一個常量池。常量池當中放置了符號引用。需要通過解析,將符號引用替換爲直接引用。
初始化
-
初始化階段是執行類構造器
<clinit>()
方法(不是普通的對象的構造器,平時看不到,也不能自己直接定義)的過程。<clinit>()
方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態語句塊中的語句合併產生的。 -
當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先發出其父類的初始化
-
虛擬機保證一個類的
<clinit>()
方法在多線程環境中被正確加鎖和同步(保證初始化時的線程安全)
我們來看一段代碼:
package cn.hanquan.classloader;
public class Loader {
public static void main(String[] args) {
A a = new A();
System.out.println(A.width);
}
}
class A {
public static int width = 100;
static {
System.out.println("靜態初始化塊:A");
width = 300;
}
public A() {
System.out.println("A的構造器");
}
}
以上代碼運行時,產生如下過程:
再加一個父類:
package cn.hanquan.classloader;
public class Loader {
static {
System.out.println("Loader的初始化塊");
}
public static void main(String[] args) {
System.out.println("main中的第1條語句");
A a1 = new A();
System.out.println("A.width = " + A.width);
A a2 = new A();
a2.width = 666;//不建議這麼做 應該使用A.width
System.out.println("A.width = " + A.width);
}
}
class A extends A_Father {
public static int width = 100;
static {
System.out.println("執行靜態初始化塊:A");
width = 300;
}
public A() {
System.out.println("執行A的構造器");
}
}
class A_Father {
static {
System.out.println("執行父類靜態初始化塊:A_Father");
}
}
Loader的初始化塊
main中的第1條語句
執行父類靜態初始化塊:A_Father
執行靜態初始化塊:A
執行A的構造器
A.width = 300
執行A的構造器
A.width = 666
類加載全過程
類的主動引用,一定會發生初始化
- new一個類的對象
A a1 = new A();
- 調用類的靜態成員(除了final常量)和靜態方法
- 使用
java.lang.reflect
包的方法對類進行反射調用Class.forName("cn.hanquan.classloader.A);
- 當虛擬機啓動,
java Hello
,則一定會初始化Hello類,說白了就是先啓動main方法所在的類 - 當初始化一個類,如果其父類沒有被初始化,則會先初始化它的父類
類的被動引用,不會發生類的初始化
- 調用
final
常量,不會發生初始化public static final int len=100;System.out.println(A.len);
因爲常量是放在常量池中的,並不會初始化所在的類。 - 通過數組定義類引用,也不會初始化
A[] arr = new A[10];
- 當訪問一個靜態域(field)時,只有深圳聲明這個域的類纔會被初始化。
( 通過子類訪問父類的靜態域的時候,子類本身不會被初始化。)