【JVM類加載】類的加載,連接與初始化
類加載
- 在java代碼中,類型的加載,連接與初始化過程都是在程序運(yùn)行期間完成的(類class文件信息在編譯期間已經(jīng)確定好)。
- 提供了更大的靈活性,增加了更多的可能性。
類加載器
java虛擬機(jī)與程序的生命周期:
在如下幾種情況下,java虛擬機(jī)將結(jié)束生命周期:
- 執(zhí)行System.exit()方法
- 程序正常執(zhí)行結(jié)束
- 程序在執(zhí)行過程中遇到了異?;蝈e(cuò)誤向上拋出異常拋到main入口程序終止
- 由于操作系統(tǒng)出現(xiàn)錯(cuò)誤而導(dǎo)致java虛擬機(jī)進(jìn)程終止
類的加載流程三部分
加載:查找并加載類的二進(jìn)制數(shù)據(jù)
連接 :
驗(yàn)證:確保被加載的類的正確性(字節(jié)碼是否符合JVM的規(guī)范)
準(zhǔn)備:為類的靜態(tài)變量分配內(nèi)存,并將其初始化值設(shè)置為默認(rèn)值
如 public static int a = 1; 先設(shè)置為默認(rèn)值0.
解析: 把類中的符號(hào)引用轉(zhuǎn)換成直接引用
初始化:為類的靜態(tài)變量賦予正確的初始值未開發(fā)人員定義的靜態(tài)變量賦予真實(shí)的值。
解析過程
類的使用與卸載
- 使用
- 卸載
類的加載,連接與初始化
java程序?qū)︻惖氖褂梅绞娇煞譃閮煞N:
- 主動(dòng)使用
所有的java虛擬機(jī)實(shí)現(xiàn)必須在每個(gè)類或接口被java程序“首次主動(dòng)使用”時(shí)才初始化他們。
- 被動(dòng)使用
類的加載,連接,初始化。
初始化和實(shí)例化的區(qū)別?
初始化只是類加載,只執(zhí)行一次,即只有有一個(gè)類對(duì)象(注意不是實(shí)例對(duì)象),無論你以后怎么個(gè)new法,新new的都是實(shí)例對(duì)象。
Object o = null; 或者 Objects o;// 這個(gè)叫初始化,只在棧內(nèi)存中存在,并沒有獲取到實(shí)際的引用o = new Object(); // 這是實(shí)例化()。
spring bean的初始化(區(qū)別類的初始化實(shí)例化)。
spring bean的初始化,是對(duì)實(shí)例化出來的對(duì)象進(jìn)行填充初始化。
實(shí)例化(Instantiation)—-實(shí)例化的過程是一個(gè)創(chuàng)建Bean的過程,即調(diào)用Bean的構(gòu)造函數(shù),單例的Bean放入單例池中。
初始化(Initialization)—-初始化的過程是一個(gè)賦值的過程,即調(diào)用類對(duì)象的setter,設(shè)置類對(duì)象的屬性 區(qū)別類加載 spring bean實(shí)例在前初始化在后。
主動(dòng)使用(七種)
- 創(chuàng)建實(shí)例類
- 訪問某個(gè)類或接口的靜態(tài)變量,或者對(duì)該靜態(tài)變量賦值
- 調(diào)用類的靜態(tài)方法
- 反射
- 初始化一個(gè)類的子類
- java虛擬機(jī)啟啟動(dòng)時(shí)被表明為啟動(dòng)類的類
- JDK1.7開始提供的動(dòng)態(tài)語言支持:java.lang.invoke.MethodHandle實(shí)例的解析結(jié)果REF_getStitac,REF_putStatic,REF_invokeStatic句柄對(duì)應(yīng)的類沒有初始化則初始化
助記符:
助記符
- getstatic 初始化時(shí)訪問靜態(tài)變量。
- putstatic 初始化時(shí)給靜態(tài)方法賦值。
- invokestatic 初始化時(shí)調(diào)用靜態(tài)方法。
/**
* 1.System.out.println(MyParent1.str)對(duì)于靜態(tài)字段來說,只有定義了該字段的類才會(huì)被初始化
* 2.System.out.println(MyChild1.str2)對(duì)于子類被初始化,要求其父類全部被初始化完畢
*/
public class Test01 {
public static void main(String[] args) {
System.out.println(MyParent1.str); //調(diào)用父類的靜態(tài)變量時(shí) 子類沒有被初始化
// System.out.println(MyChild1.str2); //全部初始化
}
}
class MyParent1{
public static String str = "hello word";
static {
System.out.println("MyParent1 static block");
}
}
class MyChild1 extends MyParent1{
public static String str2 = "welecome";
static {
System.out.println("MyChild1 static block");
}
}
除了以上7種情況,其他使用java類的方式都被看做是對(duì)類的被動(dòng)使用,都不會(huì)導(dǎo)致類的初始化。
類的加載
類的加載指的是將類的.class文件中的二進(jìn)制數(shù)據(jù)讀入到內(nèi)存中,將其放在運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi),然后在內(nèi)存中創(chuàng)建一個(gè)java.lang.Class對(duì)象(規(guī)范化并未說明Class對(duì)象位于哪里,HotSpot虛擬機(jī)將其放在方法區(qū)中)用來封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)。
記載.class文件的方式
- 從本地系統(tǒng)中直接加載(自己編寫的)。
- 通過網(wǎng)絡(luò)載.class文件。
- 從zip.jar等歸檔文件中加載.class文件(maven)。
- 將源文件動(dòng)態(tài)編譯為.class文件(動(dòng)態(tài)代理,cglib)。
- 從專有的數(shù)據(jù)庫(kù)中提取.class文件。
//常量在編譯階段會(huì)存入到調(diào)用這個(gè)常量的方法所在的類的常量池中
//本質(zhì)上,調(diào)用類并沒有直接引用到定義常量的類,因此并不會(huì)觸發(fā) 定義常量的類的初始化
// 注意:這里指的是將常量存放到了Test2的常量池中,之后Test2與MyParent2就沒有任何關(guān)系了
// 甚至,我們可以將MyParent2的class文件刪除
/**
* javap -c 查看助記符
* ldc標(biāo)識(shí)將int,float或是String類型的常量值從常量池中推送到棧頂
* bipush標(biāo)識(shí)將單字節(jié)(-128 - 127)的常量值推送至棧頂
* sipush表示將一個(gè)短整型常量值(-32768 - 32767)推送至棧頂
* iconst_1表示將int類型1推送至棧頂(iconst_1 ~ iconst_5)
* iconst_m1表示將int類型-1推送至棧頂
*/
public class Test2 {
public static void main(String[] args) {
System.out.println(MyParent2.i);
}
}
class MyParent2{
public static final String str = "hello world";
public static final int i = 128;
static {
System.out.println("Myparent2 static block");
}
}
/*
當(dāng)一個(gè)常量的值并非編譯期間可以確定的,那么其值就不會(huì)被放到調(diào)用類的常量池當(dāng)中,
這時(shí)在程序運(yùn)行時(shí),會(huì)導(dǎo)致主動(dòng)使用這個(gè)常量所在的類,顯然會(huì)導(dǎo)致這個(gè)類被初始化。
*/
public class Test3 {
public static void main(String[] args) {
System.out.println(MyParent3.STRING);
}
}
class MyParent3{
public static final String STRING = UUID.randomUUID().toString();
static {
System.out.println("MyParent3 static code");
}
}
接口:
/*
當(dāng)一個(gè)接口在初始化時(shí),并不要求其父接口都完成了初始化
只有在真正使用到父接口的時(shí)候 (引用接口中定義的常量時(shí)),才會(huì)初始化
*/
public class Test5 {
public static void main(String[] args) {
System.out.println(MyChild5.b);
}
}
interface MyParcnt5{
public static final int a = 5;
}
interface MyChild5 extends MyParcnt5{
public static int b = new Random().nextInt(4);
}
public class Test6 {
public static void main(String[] args) {
Singleton singleton = Singleton.getSingleton();
System.out.println("i=" + Singleton.i);
System.out.println("b=" + Singleton.b);
}
}
//當(dāng)類加載時(shí) 程序是自上而下執(zhí)行的
// 靜態(tài)變量:類變量,類的所有實(shí)例都共享,我們只需知道,在方法區(qū)有個(gè)靜態(tài)區(qū),靜態(tài)區(qū)專門存放靜態(tài)變量和靜態(tài)塊。
class Singleton {
public static int i;
public static Singleton singleton = new Singleton();//又初始化
private Singleton() {
i++;
b++;//準(zhǔn)備階段的意義
//此時(shí) i和b都被賦值為1
System.out.println(i);
System.out.println(b);
}
//此時(shí) b= 1 又被賦值為0
public static int b = 0;
public static Singleton getSingleton() {
return singleton;
}
}
/*
1
1
i=1
b=0
*/
注意:一定要將初始化和實(shí)例化分開 初始化時(shí)將一個(gè)類里的靜態(tài)變量附上正確的值(程序員需要賦的值) 一個(gè).clas類只初始化一次,實(shí)例化他可以多次創(chuàng)建沒new一次就是一個(gè)實(shí)例化 (初始化不一定實(shí)例化實(shí)例化一定初始化了)。
靜態(tài)變量初始化