類加載
- 在Java代碼中,類型的加載框产、連接與初始化過程都是在程序運行期間完成的
- 提供了更大的靈活性凄杯,增加了更多的可能性
Java虛擬機與程序的生命周期
jvm.PNG
在如下幾種情況下,Java虛擬機將結(jié)束生命周期
- 執(zhí)行了System.exit()方法
- 程序正常執(zhí)行結(jié)束
- 程序在執(zhí)行過程中遇到了異趁┬牛或錯誤而異常終止
- 由于操作系統(tǒng)出現(xiàn)錯誤而導致Java虛擬機進程終止
類的加載盾舌、連接與初始化
- 加載:查找并加載類的二進制數(shù)據(jù)
- 連接
- 驗證:確保被加載的類的正確性
- 準備:為類的靜態(tài)變量分配內(nèi)存,并將其初始化為默認值
- 解析:把類中的符號引用轉(zhuǎn)換為直接引用
- 初始化:為類的靜態(tài)變量賦予正確的初始值
class Test {
public static int a = 1; // 準備階段a的值為0
}
類的使用與卸載
- 使用
- 卸載
Java程序?qū)︻惖氖褂梅绞娇煞譃閮煞N
- 主動使用
- 被動使用
所有的Java虛擬機實現(xiàn)必須在每個類或接口被Java程序“首次主動使用”時才初始化他們
主動使用(七種)
- 創(chuàng)建類的實例
- 訪問某個類或接口的靜態(tài)變量蘸鲸,或者對該靜態(tài)變量賦值
- 調(diào)用類的靜態(tài)方法
- 反射(如Class.forName("com.test.Test"))
- 初始化一個類的子類
- Java虛擬機啟動時被標明為啟動類的類(Java Test)
- JDK1.7開始提供的動態(tài)語言支持: java.lang.invoke.MethodHandle實例的解析結(jié)果REF_getStatic, REF_putStatic,REF_invokeStatic句柄對應的類沒有初始化則初始化
助記符
- getStatic
- putStatic
- invokeStatic
除了以上七種情況妖谴,其他使用Java類的方式都被看作是對類的被動使用,都不會導致類的初始化
類的加載
類的加載指的是將類的.class文件中的二進制數(shù)據(jù)讀入到內(nèi)存中,將其放在運行時數(shù)據(jù)區(qū)的方法區(qū)內(nèi)酌摇,然后在內(nèi)存中創(chuàng)建一個
java.lang.Class對象(規(guī)范并未說明Class對象位于哪里膝舅,HotSpot虛擬機將其放在了方法區(qū)中)用來封裝類在方法區(qū)的數(shù)據(jù)結(jié)構-
加載.class文件的方式
- 從本地系統(tǒng)中直接加載
- 通過網(wǎng)絡下載.class文件
- 從zip,jar等歸檔文件中加載.class文件
- 從專有數(shù)據(jù)庫中提取.class文件
- 將Java源文件動態(tài)編譯為.class文件
public class MyTest1 {
public static void main(String[] args) {
// 1. 對于靜態(tài)字段來說,只有直接定義了該字段的類才會被初始化
// 2. 當一個類在初始化時窑多,要求其父類全部都已經(jīng)初始化完畢
// System.out.println(MyChild1.str);
System.out.println(MyChild1.str);
// 發(fā)現(xiàn)加載了MyChild1類仍稀,但未初始化
// [Loaded com.yhyecho.classload.MyParent1 from file:/C:/MySpace/jvm/target/classes/]
// [Loaded com.yhyecho.classload.MyChild1 from file:/C:/MySpace/jvm/target/classes/]
// JVM參數(shù)配置規(guī)則
// -XX:+TraceClassLoading, 用于追蹤類的加載信息并打印出來
// -XX:+<option>, 表示開啟option選項
// -XX:-<option>, 表示關閉option選項
// -XX:<option>=<value>, 表示將option選項的值設置為value
}
}
class MyParent1 {
static String str = "hello world";
static {
System.out.println("MyParent1 static block");
}
}
class MyChild1 extends MyParent1 {
static String str2 = "welcome";
static {
System.out.println("MyChild static block");
}
}
JVM參數(shù)配置規(guī)則
- -XX:+TraceClassLoading, 用于追蹤類的加載信息并打印出來
- -XX:+<option>, 表示開啟option選項
- -XX:-<option>, 表示關閉option選項
- -XX:<option>=<value>, 表示將option選項的值設置為value
public class MyTest2 {
public static void main(String[] args) {
// 常量在編譯階段會存入到調(diào)用這個常量的方法所在的類的常量池中,
// 本質(zhì)上,調(diào)用類并沒有直接引用到定義常量的類, 因此并不會觸發(fā)定義常量類的初始化
// 注意:這里指的是將常量存放在MyTest2的常量池中埂息,之后MyTest2與MyParent2就沒有任何關系了
// 甚至技潘,我們可以將MyParent2的class文件刪除
System.out.println(MyParent2.str);
System.out.println(MyParent2.s);
System.out.println(MyParent2.i);
System.out.println(MyParent2.m1);
System.out.println(MyParent2.m);
// 使用javap -c MyTest2.class 反編譯字節(jié)碼
// 驚喜的發(fā)現(xiàn)一些助記符
// ldc表示將int,float或是String類型的常量值從常量池中推送至棧頂
// bipush表示將單字節(jié)(-128 ~ 127) 的常量值推送至棧頂
// sipush表示將一個短整型常量值(-32768 ~ 32767) 推送至棧頂
// iconst_1表示將int類型1推送至棧頂(iconst_1 ~ iconst_5)
}
}
class MyParent2 {
// 1.
// static String str = "hello world";
// 2. 常量池
static final String str = "hello world";
static final short s = 127;
static final int i = 128;
static final int m1 = 1;
static final int m = 6;
static {
System.out.println("MyParent2 static block");
}
}
public class MyTest3 {
// 當一個常量的值并非編譯期間可以確定的千康,那么其值就不會被放到調(diào)用類的常量池中享幽,
// 這時在程序運行時,會導致主動使用這個常量所在的類,顯然會導致這個類的被初始化拾弃。
public static void main(String[] args) {
System.out.println(MyParent3.str);
}
}
class MyParent3 {
static final String str = UUID.randomUUID().toString();
static {
System.out.println("MyParent3 static code");
}
}