最近在看 Java 虛擬機(jī)類加載的知識(shí)點(diǎn),結(jié)果讓我發(fā)現(xiàn)了自己一個(gè)曾經(jīng)一直糾結(jié),又沒(méi)徹底弄懂的類加載黑洞,從而引發(fā)下面一系列的測(cè)試血案
相信面試過(guò)的你們也會(huì)見(jiàn)過(guò)類似下面測(cè)試的這幾道題
不過(guò),答案你真的理解了么?
話不多說(shuō),直接 GKD
相關(guān)學(xué)習(xí)巨佬博
站在巨佬的肩膀上
http://www.itdecent.cn/p/b6547abd0706
http://www.itdecent.cn/p/8c8d6cba1f8e
https://www.cnblogs.com/wcd144140/p/7273974.html
看完,大佬們應(yīng)該都全部理解透了...可惜我不是大佬,所以...哈哈哈 GKD 吧
下面就是測(cè)試過(guò)程種發(fā)現(xiàn)的一些疑惑點(diǎn),趕緊記錄一波.....哎,難頂
測(cè)試開(kāi)始
先思考下下面代碼輸出什么
class Singleton {
public Singleton() {
System.out.println("Singleton new instance");
}
static {
System.out.println("Singleton static block");
}
{
System.out.println("Singleton block !?。?);
}
}
public class NewTest {
public static void main(String args[]){
Singleton singleton = new Singleton();
}
}
輸出結(jié)果
Singleton static block
Singleton block ?。?!
Singleton new instance
當(dāng)然,大佬們應(yīng)該都能知道答案...畢竟,新手入門級(jí)的野怪,誰(shuí)都打得過(guò)
這個(gè)對(duì)我這小菜雞也算還比較容易理解:
加載連接過(guò)程,沒(méi)有需要處理的 static
new Singleton() 直接開(kāi)始類的初始化了,所以輸出直接按照類的初始化順序來(lái)就好了
類的初始化的執(zhí)行順序
沒(méi)有父類的情況:
1)類的靜態(tài)屬性
2)類的靜態(tài)代碼塊
3)類的非靜態(tài)屬性
4)類的非靜態(tài)代碼塊
5)構(gòu)造方法
有父類的情況:
1)父類的靜態(tài)屬性
2)父類的靜態(tài)代碼塊
3)子類的靜態(tài)屬性
4)子類的靜態(tài)代碼塊
5)父類的非靜態(tài)屬性
6)父類的非靜態(tài)代碼塊
7)父類構(gòu)造方法
8)子類非靜態(tài)屬性
9)子類非靜態(tài)代碼塊
10)子類構(gòu)造方法
這里有個(gè)小誤區(qū),是我自己的誤區(qū)~~
比如下面這個(gè)例子
class ParentSingleton{
public static int value = 100;
public ParentSingleton(){
System.out.println("ParentSingleton new instance");
}
static {
System.out.println("ParentSingleton static block");
}
{
System.out.println("ParentSingleton block !??! ");
}
}
當(dāng)要初始化上面這個(gè)類的時(shí)候,會(huì)輸出什么
如果這時(shí)候,我們只看上面的初始化順序,會(huì)覺(jué)得這樣輸出,根據(jù)順序來(lái)嘛
ParentSingleton static block
ParentSingleton block !?。?
ParentSingleton new instance
???
OMG,錯(cuò)了,這里的順序不是說(shuō),只要初始化,就要全部按照順序一一執(zhí)行。。不是這樣的
實(shí)際上只會(huì)輸出
ParentSingleton static block
如果有 創(chuàng)建這個(gè)類的實(shí)例,比如 new ParentSingleton()
才會(huì)
ParentSingleton block ?。?!
ParentSingleton new instance
是的,這里的誤區(qū),我曾經(jīng)一度搞錯(cuò)了。。。尷尬
那再看這個(gè)測(cè)試
class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {
System.out.println("Singleton new instance");
}
public static void forTest() {
}
static {
System.out.println("Singleton static block");
}
{
System.out.println("Singleton block ?。。?");
}
}
public class TestSingleton {
public static void main(String args[]){
Singleton.forTest();
}
}
看完資料的我,逐漸膨脹,畢竟100多斤的胖子,我想的輸出應(yīng)該是
Singleton static block
Singleton block ?。?!
Singleton new instance
然后運(yùn)行一看,懵逼了,結(jié)果是
Singleton block ?。。?
Singleton new instance
Singleton static block
咋回事啊,小老弟,結(jié)果亂套了...為什么不是先執(zhí)行 static 代碼塊先了
認(rèn)真想了一波,也不知道對(duì)不對(duì),只能瘋狂測(cè)試這樣子...
經(jīng)過(guò)一番測(cè)試,查看資料...最終...我覺(jué)得是這樣子的
整個(gè)的流程詳解:
執(zhí)行的第一步:Singleton.forTest();
這時(shí)候,對(duì)Singleton類進(jìn)行加載和連接,所以首先需要對(duì)它進(jìn)行加載和連接操作。
在連接-準(zhǔn)備階段,要講給靜態(tài)變量賦予默認(rèn)初始值,這里還沒(méi)到執(zhí)行 forTest
初始值是 singleton = null
加載和連接完畢之后,再進(jìn)行初始化工作
private static Singleton singleton = new Singleton();
所以執(zhí)行去到了 new Singleton(); 這里因?yàn)?new 會(huì)引起 Singleton 的初始化
需要執(zhí)行 Singleton構(gòu)造函數(shù)里面的內(nèi)容
但是又因?yàn)榉莝tatic初始化塊,這里面的代碼在創(chuàng)建java對(duì)象實(shí)例時(shí)執(zhí)行,而且在構(gòu)造器之前!?。?!就是這東西。。
所以輸出應(yīng)該是
Singleton block ?。。?
Singleton new instance
而根據(jù)類的初始化順序,要執(zhí)行 static 代碼塊,應(yīng)該輸出
Singleton static block
完成初始化后
接下來(lái)就到真正調(diào)用 forTest 方法了,方法什么都不做,沒(méi)輸出
所以,總的答案就是
Singleton block ?。?!
Singleton new instance
Singleton static block
這里最大的原因就是,連接加載的時(shí)候,要給屬性初始化,而這里的初始化又剛好是 創(chuàng)建java 實(shí)例,需要執(zhí)行構(gòu)造,執(zhí)行構(gòu)造的前面又必須先執(zhí)行 {} 大括號(hào)非 static 塊
而不是和第一個(gè)測(cè)試?yán)幽菢?,static 屬性不需要初始化,所以....
IG 永不加班,但我需要哇 繼續(xù)測(cè)試吧...
繼續(xù)測(cè)試驗(yàn)證
class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {
System.out.println("Singleton new instance");
}
public static Singleton getSingleton() {
return new Singleton();
}
static {
System.out.println("Singleton static block");
}
{
System.out.println("Singleton block ?。。?");
}
}
public class TestSingleton {
public static void main(String args[]){
Singleton singleton = Singleton.getSingleton();
}
}
輸出結(jié)果
emm, 再次根據(jù)上面自己的理解,走一遍
應(yīng)該是
Singleton block !??!
Singleton new instance
Singleton static block
Singleton block !??!
Singleton new instance
這里后面第二次 new 為啥不引起第二次 類的初始化?? 因?yàn)橐粋€(gè)類只能初始化一次啊
new 只是創(chuàng)建實(shí)例,不再初始化了
所以在調(diào)用 getSingleton 的時(shí)候,只創(chuàng)建實(shí)例就好了,而創(chuàng)建實(shí)例就是
Singleton block ?。?!
Singleton new instance
在同一個(gè)類加載器下面只能初始化類一次,如果已經(jīng)初始化了就不必要初始化了.
為什么只初始化一次呢?類加載的最終結(jié)果就是在堆中存有唯一一個(gè)Class對(duì)象,我們通過(guò)Class對(duì)象找到的那個(gè)唯一的
噢? 運(yùn)行看一手,丟 對(duì)了。。。。
還有 存在 final 的時(shí)候,和存在父類的時(shí)候,下面慢慢再測(cè)試驗(yàn)證....
繼續(xù)測(cè)試
class Singleton extends ParentSingleton {
public Singleton() {
System.out.println("Singleton new instance");
}
static {
System.out.println("Singleton static block");
}
{
System.out.println("Singleton block ?。?! ");
}
}
class ParentSingleton{
public ParentSingleton(){
System.out.println("ParentSingleton new instance");
}
static {
System.out.println("ParentSingleton static block");
}
{
System.out.println("ParentSingleton block !??! ");
}
}
public class TestSingleton {
public static void main(String args[]){
Singleton singleton = new Singleton();
}
}
輸出結(jié)果
這個(gè),很明了,還是按照上面的類的初始化,有父類的情況按順序調(diào)用,輸出
ParentSingleton static block
Singleton static block
ParentSingleton block ?。。?
ParentSingleton new instance
Singleton block ?。?!
Singleton new instance
繼續(xù)測(cè)試
那個(gè)人 又來(lái)了。。。改成和上面沒(méi)有父類一樣的情況
class Singleton extends ParentSingleton {
private static Singleton singleton = new Singleton();
private Singleton() {
System.out.println("Singleton new instance");
}
public static Singleton getSingleton() {
return singleton;
}
static {
System.out.println("Singleton static block");
}
{
System.out.println("Singleton block ?。?! ");
}
}
class ParentSingleton{
public ParentSingleton(){
System.out.println("ParentSingleton new instance");
}
static {
System.out.println("ParentSingleton static block");
}
{
System.out.println("ParentSingleton block ?。。?");
}
}
public class TestSingleton {
public static void main(String args[]){
Singleton singleton = Singleton.getSingleton();
}
}
輸出結(jié)果
這里,就開(kāi)始懵了。。。有點(diǎn)
先看結(jié)果
ParentSingleton static block
ParentSingleton block ?。?!
ParentSingleton new instance
Singleton block !??!
Singleton new instance
Singleton static block
其實(shí),很容易看清了,現(xiàn)在,再走一遍流程吧
執(zhí)行到 Singleton.getSingleton() 時(shí)
先加載 Singleton ,這時(shí)因?yàn)?Singleton 有父類,需要需要加載父類先
加載父類 ParentSingleton,根據(jù)加載流程,在連接-準(zhǔn)備階段,要講給靜態(tài)變量賦予默認(rèn)初始值,但父類沒(méi)有 static 屬性需要賦值初始化什么的,但是根據(jù)順序,需要初始化static 代碼塊
ParentSingleton static block
這時(shí)候回到 子類的 加載流程
根據(jù)連接-準(zhǔn)備階段,子類有需要處理的屬性 private static Singleton singleton = new Singleton()
賦值默認(rèn)值先,singleton = null
然后初始化 singleton = new Singleton()
根據(jù)上面的經(jīng)驗(yàn),這里是創(chuàng)建實(shí)例 ,并引起初始化,正常應(yīng)該是
Singleton block !??!
Singleton new instance
Singleton static block
但是,重點(diǎn)來(lái)了 ?。?
類實(shí)例創(chuàng)建過(guò)程:按照父子繼承關(guān)系進(jìn)行初始化,首先執(zhí)行父類的初始化塊部分
然后是父類的構(gòu)造方法;再執(zhí)行本類繼承的子類的初始化塊,最后是子類的構(gòu)造方法
也就是
ParentSingleton block !?。?
ParentSingleton new instance
同時(shí)子類的初始化,因?yàn)槌跏蓟宇愃懈割悾孕枰瘸跏蓟割悾ǖ沁@里因?yàn)楦割愐呀?jīng)初始化了,就不再初始化了)
所以結(jié)果是:
ParentSingleton static block
ParentSingleton block ?。?!
ParentSingleton new instance
Singleton block !??!
Singleton new instance
Singleton static block
最終測(cè)試
class Singleton extends ParentSingleton {
private static Singleton singleton = new Singleton();
private Singleton() {
System.out.println("Singleton new instance");
}
public static Singleton getSingleton() {
return singleton;
}
static {
System.out.println("Singleton static block");
}
{
System.out.println("Singleton block !?。?");
}
}
class ParentSingleton{
private static ParentSingleton parentSingleton = new ParentSingleton();
public ParentSingleton(){
System.out.println("ParentSingleton new instance");
}
static {
System.out.println("ParentSingleton static block");
}
{
System.out.println("ParentSingleton block ?。?! ");
}
}
public class TestSingleton {
public static void main(String args[]){
Singleton singleton = Singleton.getSingleton();
}
}
測(cè)試結(jié)果
ParentSingleton block !??!
ParentSingleton new instance
ParentSingleton static block
ParentSingleton block ?。。?
ParentSingleton new instance
Singleton block ?。?!
Singleton new instance
Singleton static block
加載一個(gè)類時(shí),先加載父類
按照先加載,創(chuàng)建實(shí)例,初始化,這個(gè)順序就發(fā)現(xiàn) 很通順的寫(xiě)出答案了
哈哈哈哈哈,終于清楚了 所以一切的一切,都是創(chuàng)建實(shí)例這個(gè)東西。。搞得我頭暈
部分特殊不引起類初始化記錄
先記錄下吧
1. 通過(guò)子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類初始化,對(duì)于靜態(tài)字段,只有直接定義這個(gè)字段的類才會(huì)被初始化
2. 通過(guò)數(shù)組定義來(lái)引用類,不會(huì)觸發(fā)此類的初始化
3. 常量在編譯階段會(huì)存入調(diào)用類的常量池中,本質(zhì)上并沒(méi)有直接引用到定義常量的類,因此不會(huì)觸發(fā)定義常量的類的初始化
4. public static final int x =6/3; 能夠在編譯時(shí)期確定的,叫做編譯常量,不會(huì)引起類的初始化!!!
5. public static final int x =new Random().nextInt(100); 運(yùn)行時(shí)才能確定下來(lái)的,叫做運(yùn)行時(shí)常量,運(yùn)行常量會(huì)引起類的初始化!!!
引起類初始化記錄
在虛擬機(jī)規(guī)范中使用了一個(gè)很強(qiáng)烈的限定語(yǔ):“有且僅有”,這5種場(chǎng)景中的行為稱為對(duì)類進(jìn)行主動(dòng)引用。除此之外,所有引用類的方式都不會(huì)觸發(fā)初始化,稱為被動(dòng)引用
5種必須初始化的場(chǎng)景如下
1. 遇到new、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時(shí),如果類沒(méi)有初始化,則需要先觸發(fā)其初始化
這4條指令對(duì)應(yīng)的的常見(jiàn)場(chǎng)景分別是:使用new關(guān)鍵字實(shí)例化對(duì)象、讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候,以及調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候
注:靜態(tài)內(nèi)容是跟類關(guān)聯(lián)的而不是類的對(duì)象
2. 使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候,如果類沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化
注:反射機(jī)制是在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法
對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法和屬性
這種動(dòng)態(tài)獲取的信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱為java語(yǔ)言的反射機(jī)制,這相對(duì)好理解為什么需要初始化類
3. 當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其父類的初始化
注:子類執(zhí)行構(gòu)造函數(shù)前需先執(zhí)行父類構(gòu)造函數(shù)
4. 當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類
注:main方法是程序的執(zhí)行入口
5. 當(dāng)使用JDK1.7的動(dòng)態(tài)語(yǔ)言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個(gè)方法句柄所對(duì)應(yīng)的類沒(méi)有進(jìn)行過(guò)初始化。則需要先觸發(fā)其初始化
注:JDK1.7的一種新增的反射機(jī)制,都是對(duì)類的一種動(dòng)態(tài)操作
這回,以后看代碼的時(shí)候,就不會(huì)再被這些執(zhí)行加載順序弄混了,對(duì)優(yōu)化代碼可能還是有幫助的吧
再不說(shuō),也能再讓我看到這些測(cè)試題,或者問(wèn)我加載的過(guò)程,怎么也能處理回答個(gè)7788了吧
可能其中個(gè)人理解有部分紕漏,還請(qǐng)大佬們指出~~蟹蟹鴨
后面還要去驗(yàn)證測(cè)試下面這些情況
1. 子類引用指向父類
2. ...等等
任重而道遠(yuǎn),我是橋豆麻袋,下回再見(jiàn)~