虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存浙于,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換挟纱、解析和初始化羞酗,最終可以變成被虛擬機(jī)使用的Java類型,這就是虛擬機(jī)的類加載機(jī)制紊服。
在java中檀轨,類型的加載、連接和初始化都是在程序運(yùn)行期間完成的欺嗤,不同于c/c++,這種策略的缺點(diǎn)就是在類加載的時(shí)候會(huì)造成性能開(kāi)銷参萄,但是它保證了java應(yīng)用程序的靈活性。例如:用戶可以通過(guò)自定義的類加載器煎饼,讓一個(gè)應(yīng)用程序可以在運(yùn)行時(shí)從網(wǎng)絡(luò)或者其它地方加載一個(gè)二進(jìn)制流作為程序代碼的一部分拧揽。例如JSP技術(shù)。
一腺占、類的加載時(shí)機(jī)與過(guò)程
1. 類的加載時(shí)機(jī)
類從被加載到虛擬機(jī)內(nèi)存中開(kāi)始淤袜,直到卸載出內(nèi)存為止,它的整個(gè)生命周期包括了:加載衰伯、驗(yàn)證铡羡、準(zhǔn)備、解析意鲸、初始化烦周、使用和卸載這7個(gè)階段尽爆。其中,驗(yàn)證读慎、準(zhǔn)備和解析這三個(gè)部分統(tǒng)稱為連接(linking)漱贱。
2. 何時(shí)開(kāi)始類的初始化
什么情況下需要開(kāi)始類加載過(guò)程的第一個(gè)階段:"加載"。虛擬機(jī)規(guī)范中并沒(méi)強(qiáng)行約束夭委,這點(diǎn)可以交給虛擬機(jī)的的具體實(shí)現(xiàn)自由把握幅狮,但是對(duì)于初始化階段虛擬機(jī)規(guī)范是嚴(yán)格規(guī)定了如下幾種情況,如果類未初始化會(huì)對(duì)類進(jìn)行初始化株灸。
- 創(chuàng)建類的實(shí)例
- 訪問(wèn)類的靜態(tài)變量(除常量即被
final
表示的靜態(tài)變量)崇摄,因?yàn)?strong>常量一種特殊的變量,因?yàn)榫幾g器把他們當(dāng)作值(value)而不是域(field)來(lái)對(duì)待慌烧。如果你的代碼中用到了常變量(constant variable)逐抑,編譯器并不會(huì)生成字節(jié)碼來(lái)從對(duì)象中載入域的值,而是直接把這個(gè)值插入到字節(jié)碼中屹蚊。這是一種很有用的優(yōu)化厕氨,但是如果你需要改變final域的值那么每一塊用到那個(gè)域的代碼都需要重新編譯。 - 訪問(wèn)類的靜態(tài)方法
- 反射(Class.forName("com.zcl.main"))
- 當(dāng)初始化一個(gè)類時(shí)汹粤,發(fā)現(xiàn)其父類還未初始化腐巢,則先對(duì)父類進(jìn)行初始化
- 虛擬機(jī)啟動(dòng)時(shí),先對(duì)
main()
方法的那個(gè)類進(jìn)行初始化
以上情況稱為稱對(duì)一個(gè)類進(jìn)行“主動(dòng)引用”玄括,除此種情況之外冯丙,均不會(huì)觸發(fā)類的初始化,稱為“被動(dòng)引用”
接口的加載過(guò)程與類的加載過(guò)程稍有不同遭京。接口中不能使用static{}塊胃惜。當(dāng)一個(gè)接口在初始化時(shí),并不要求其父接口全部都完成了初始化哪雕,只有真正在使用到父接口時(shí)(例如引用接口中定義的常量)才會(huì)初始化船殉。
被動(dòng)引用的例子
- 子類調(diào)用父類的靜態(tài)變量,子類不會(huì)被初始化斯嚎。只有父類被初始化利虫。。對(duì)于靜態(tài)字段堡僻,只有直接定義這個(gè)字段的類才會(huì)被初始化.
- 通過(guò)數(shù)組定義來(lái)引用類糠惫,不會(huì)觸發(fā)類的初始化
- 訪問(wèn)類的常量,不會(huì)初始化類
class SuperClass {
static {
System.out.println("superclass init");
}
public static int value = 123;
}
class SubClass extends SuperClass {
static {
System.out.println("subclass init");
}
}
public class Test {
public static void main(String[] args) {
System.out.println(SubClass.value);// 被動(dòng)應(yīng)用1
SubClass[] sca = new SubClass[10];// 被動(dòng)引用2
}
}
程序輸出:證明了被動(dòng)引用1 2
superclass init
123
class ConstClass {
static {
System.out.println("ConstClass init");
}
public static final String HELLOWORLD = "hello world";
}
public class Test {
public static void main(String[] args) {
System.out.println(ConstClass.HELLOWORLD);// 調(diào)用類常量
}
}
程序輸出:證明了被動(dòng)引用3
hello world
2. 類的加載過(guò)程
2.1驗(yàn)證
驗(yàn)證class文件的內(nèi)容是否符合jvm虛擬機(jī)的標(biāo)準(zhǔn)要求钉疫,包括class文件的格式驗(yàn)證和語(yǔ)義驗(yàn)證等
2.2 準(zhǔn)備
準(zhǔn)備階段是為類的靜態(tài)變量分配內(nèi)存并將其初始化為默認(rèn)值硼讽,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配。準(zhǔn)備階段不分配類中的實(shí)例變量的內(nèi)存牲阁,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在Java堆中固阁。
public static int value=123;//在準(zhǔn)備階段value初始值為0 壤躲。在初始化階段才會(huì)變?yōu)?23 。
2.3 解析
該階段主要完成符號(hào)引用到直接引用的轉(zhuǎn)換動(dòng)作备燃,例如:根據(jù)類的全限定名找到該類具體的地址碉克。解析動(dòng)作并不一定在初始化動(dòng)作完成之前,也有可能在初始化之后并齐。
2.4 初始化
初始化順序:
我們大家都知道漏麦,對(duì)于靜態(tài)變量、靜態(tài)初始化塊冀膝、變量、初始化塊霎挟、構(gòu)造器窝剖,它們的初始化順序以此是(靜態(tài)變量、靜態(tài)初始化塊)>(變量酥夭、初始化塊)>構(gòu)造器赐纱。
類初始化是類加載過(guò)程的最后一步,前面的類加載過(guò)程熬北,除了在加載階段用戶應(yīng)用程序可以通過(guò)自定義類加載器參與之外疙描,其余動(dòng)作完全由虛擬機(jī)主導(dǎo)和控制。到了初始化階段讶隐,才真正開(kāi)始執(zhí)行類中定義的Java程序代碼起胰。
初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過(guò)程。<clinit>()方法是由編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊(static{}塊)中的語(yǔ)句合并產(chǎn)生的巫延。
題目分析
class SingleTon {
private static SingleTon singleTon = new SingleTon();
public static int count1;
public static int count2 = 0;
private SingleTon() {
count1++;
count2++;
}
public static SingleTon getInstance() {
return singleTon;
}
}
public class Test {
public static void main(String[] args) {
SingleTon singleTon = SingleTon.getInstance();
System.out.println("count1=" + singleTon.count1);
System.out.println("count2=" + singleTon.count2);
}
}
1:SingleTon singleTon = SingleTon.getInstance();調(diào)用了類的SingleTon調(diào)用了類的靜態(tài)方法效五,觸發(fā)類的初始化
2:類加載的時(shí)候在準(zhǔn)備過(guò)程中為類的靜態(tài)變量分配內(nèi)存并初始化默認(rèn)值 singleton=null count1=0,count2=0
3:類初始化,為類的靜態(tài)變量賦值和執(zhí)行靜態(tài)代碼塊炉峰。singleton賦值為new SingleTon()調(diào)用類的構(gòu)造方法
4:調(diào)用類的構(gòu)造方法后count=1;count2=1
5:繼續(xù)為count1與count2賦值,此時(shí)count1沒(méi)有賦值操作,所有count1為1,但是count2執(zhí)行賦值操作就變?yōu)?
若將代碼修改為下:
class Singleton {
private static Singleton singleTon = new Singleton();
public static int count1 = 1;
public int count2 = 2 ;
private Singleton() {
System.out.println(count1 + " - "+count2);
count1++;
count2++;
}
public static Singleton getInstance() {
return singleTon;
}
}
public class Test {
public static void main(String[] args) {
Singleton singleTon = Singleton.getInstance();
System.out.println("count1=" + singleTon.count1);
System.out.println("count2=" + singleTon.count2);
}
}
則有結(jié)果為:
0 - 2
count1=1
count2=3
分析:因?yàn)槠胀ǔA恐挥性趯?duì)象被實(shí)例化后才分配內(nèi)存空間進(jìn)行賦值畏妖,所有當(dāng)進(jìn)行new Sigleton()
時(shí),count2已經(jīng)被賦值為2疼阔,而靜態(tài)變量在類加載的準(zhǔn)備階段已經(jīng)被初始化為0戒劫,這時(shí)還沒(méi)有被進(jìn)行賦值操作,所以最終結(jié)果為賦值后的結(jié)果1
本文摘自https://blog.csdn.net/m0_38075425/article/details/81627349
https://www.cnblogs.com/shinubi/articles/6116993.html