Java類(lèi)的加載時(shí)機(jī)
必須初始化的四種情況
有四種情況類(lèi)是必須要進(jìn)行初始化的,對(duì)于這四種情況原文描述如下:
但是對(duì)于初始化階段,虛擬機(jī)規(guī)范則是嚴(yán)格規(guī)定了有且只有4種情況必須立即對(duì)類(lèi)進(jìn)行初始化,而加載、驗(yàn)證、準(zhǔn)備自然需要在此之前開(kāi)始。
- 1:遇到new、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時(shí),如果類(lèi)沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化。生成這4條指令最常見(jiàn)的java代碼場(chǎng)景是:使用new關(guān)鍵字實(shí)例化對(duì)象的時(shí)候、讀取或設(shè)置一個(gè)類(lèi)的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候,以及調(diào)用一個(gè)類(lèi)的靜態(tài)方法的時(shí)候。
- 2:使用java.lang.reflect包的方法對(duì)類(lèi)進(jìn)行反射調(diào)用的時(shí)候,如果類(lèi)沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化。
- 3:當(dāng)初始化一個(gè)類(lèi)的時(shí)候,如果發(fā)現(xiàn)其父類(lèi)還沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其父類(lèi)的初始化。
- 4:當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(lèi)(包含main()方法的那個(gè)類(lèi)),虛擬機(jī)會(huì)先初始化這個(gè)主類(lèi)。
以上四點(diǎn)我們一一用代碼來(lái)驗(yàn)證,第一點(diǎn)里面說(shuō)到了四種初始化的場(chǎng)景,分別是:
- ①用new關(guān)鍵字實(shí)例化對(duì)象
- ②讀取類(lèi)靜態(tài)字段
- ③設(shè)置類(lèi)的靜態(tài)字段
- ④調(diào)用一個(gè)類(lèi)的靜態(tài)方法
在驗(yàn)證之前需要達(dá)成一個(gè)共識(shí):虛擬機(jī)在初始化類(lèi)時(shí)會(huì)執(zhí)行static語(yǔ)句塊中的操作,因此我們可以根據(jù)靜態(tài)語(yǔ)句塊中的代碼是否執(zhí)行了來(lái)判斷類(lèi)是否加載。為此我創(chuàng)建了一個(gè)SubClass類(lèi)
package com.test.jvm.classloading;
/**
* @author fc
*/
public class SubClass {
static{
System.out.println("子類(lèi)初始化");
}
public static int a = 10;
public static int getA(){
return a;
}
}
在main方法中分別執(zhí)行(每次執(zhí)行一條)以下四條代碼來(lái)模擬上面四個(gè)場(chǎng)景
package com.test.jvm.classloading;
/**
* @author fc
*/
public class Main {
public static void main(String[] args) {
SubClass subClass = new SubClass();
System.out.println(SubClass.a);
SubClass.getA();
SubClass.a = 30;
}
}
結(jié)果不出所料,輸出結(jié)果都包含"子類(lèi)初始化",說(shuō)明以上四種方式確實(shí)可以會(huì)觸發(fā)類(lèi)的初始化。
接下來(lái)看第二點(diǎn),對(duì)類(lèi)進(jìn)行反射調(diào)用時(shí)會(huì)觸發(fā)類(lèi)的初始化
package com.test.jvm.classloading;
/**
* @author fc
*/
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("com.test.jvm.classloading.SubClass");
}
}
以上的反射調(diào)用同樣正常輸出了"子類(lèi)初始化"。
第三點(diǎn)如果父類(lèi)沒(méi)有進(jìn)行初始化,則要先觸發(fā)父類(lèi)的初始化,再創(chuàng)建一個(gè)父類(lèi),并且讓之前的子類(lèi)繼承父類(lèi)
package com.test.jvm.classloading;
/**
* @author fc
*/
public class SuperClass {
static {
System.out.println("父類(lèi)初始化");
}
public static int b = 20;
}
package com.test.jvm.classloading;
/**
* @author fc
*/
public class SubClass extends SuperClass {
static{
System.out.println("子類(lèi)初始化");
}
public static int a = 10;
public static int getA(){
return a;
}
}
這時(shí)我們?cè)俅螆?zhí)行上面的main方法里面的任意一條測(cè)試語(yǔ)句,這時(shí)發(fā)現(xiàn)在原來(lái)的輸出"子類(lèi)初始化"前輸出了"父類(lèi)初始化",說(shuō)明了兩點(diǎn):①父類(lèi)同樣會(huì)初始化;②父類(lèi)會(huì)先于子類(lèi)初始化。
第四點(diǎn)虛擬機(jī)會(huì)先初始化包含main方法的主類(lèi),這時(shí)我們?cè)谥黝?lèi)中加入靜態(tài)代碼塊
package com.test.jvm.classloading;
/**
* @author fc
*/
public class Main {
static {
System.out.println("初始化主類(lèi)");
}
public static void main(String[] args) throws ClassNotFoundException {
SubClass subClass = new SubClass();
}
}
可以看到輸出結(jié)果如下,完全印證了第四點(diǎn)。

不主動(dòng)進(jìn)行初始化
而對(duì)于不會(huì)主動(dòng)進(jìn)行初始化的情況在該書(shū)中也有以下幾種情況
第一種是通過(guò)子類(lèi)類(lèi)名調(diào)用父類(lèi)靜態(tài)代碼(包括靜態(tài)方法和靜態(tài)變量)不會(huì)進(jìn)行初始化,以下也通過(guò)代碼進(jìn)行說(shuō)明
package com.test.jvm.classloading;
/**
* @author fc
*/
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
System.out.println(SubClass.b);
}
}
輸出如下,可以看到只初始化了父類(lèi)而沒(méi)有初始化子類(lèi)。

第二種是通過(guò)數(shù)組來(lái)創(chuàng)建對(duì)象不會(huì)觸發(fā)此類(lèi)的初始化
package com.test.jvm.classloading;
/**
* @author fc
*/
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
SuperClass[] supers = new SuperClass[10];
}
}
輸出為空。

第三種是調(diào)用final修飾的常量不會(huì)觸發(fā)類(lèi)的初始化,為此我在父類(lèi)中加了一個(gè)常量
package com.test.jvm.classloading;
/**
* @author fc
*/
public class SuperClass {
static {
System.out.println("父類(lèi)初始化");
}
public static int b = 20;
public final static String STATE = "常量";
}
package com.test.jvm.classloading;
/**
* @author fc
*/
public class Main {
public static void main(String[] args) {
System.out.println(SuperClass.STATE);
}
}
可以看到輸出結(jié)果只是打印了常量的值,并沒(méi)有初始化這個(gè)類(lèi)。

補(bǔ)充
到這里對(duì)于書(shū)中描述的類(lèi)的加載時(shí)機(jī)都已經(jīng)用例子說(shuō)明了,接下來(lái)展示一個(gè)在博主Boblim那看到的一個(gè)例子
/**
* @author fc
*/
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.getInstance();
System.out.println("count1=" + SingleTon.count1);
System.out.println("count2=" + SingleTon.count2);
}
}
輸出count1=1,count2=0,關(guān)于為什么會(huì)輸出這個(gè)結(jié)果在那篇鏈接的博客已經(jīng)做了詳細(xì)的說(shuō)明,同時(shí)這個(gè)輸出結(jié)果也很好地佐證了下面這句話
類(lèi)構(gòu)造器<clinit>()方法是由編譯器自動(dòng)收集類(lèi)中的所有類(lèi)變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊(static塊)中的語(yǔ)句合并產(chǎn)生的,編譯器收集的順序是由語(yǔ)句在源文件中出現(xiàn)的順序所決定的。
正是給類(lèi)變量賦值時(shí)是按照順序進(jìn)行的,所以上面count2又會(huì)被重新賦值為0,才導(dǎo)致這個(gè)輸出結(jié)果。
以上所述是小編給大家介紹的Java類(lèi)的加載時(shí)機(jī),希望對(duì)大家有所幫助。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Java實(shí)現(xiàn)插入排序,希爾排序和歸并排序
這篇文章主要為大家詳細(xì)介紹了插入排序,希爾排序和歸并排序的多種語(yǔ)言的實(shí)現(xiàn)(JavaScript、Python、Go語(yǔ)言、Java),感興趣的小伙伴可以了解一下2022-12-12
Java?HashMap中除了死循環(huán)之外的那些問(wèn)題
這篇文章主要介紹了Java?HashMap中除了死循環(huán)之外的那些問(wèn)題,這些問(wèn)題大致可以分為兩類(lèi),程序問(wèn)題和業(yè)務(wù)問(wèn)題,下面文章我們一個(gè)一個(gè)來(lái)看,需要的小伙伴可以參考一下2022-05-05
Java8新特性lambda表達(dá)式有什么用(用法實(shí)例)
這篇文章主要介紹了Java8新特性lambda表達(dá)式有什么用,著重以實(shí)例講解lambda表達(dá)式,需要的朋友可以參考下2014-06-06
Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(64)
下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你2021-09-09
關(guān)于Java中@SuppressWarnings的正確使用方法
這篇文章主要介紹了關(guān)于Java中@SuppressWarnings的正確使用方法,@SuppressWarnings注解主要用在取消一些編譯器產(chǎn)生的警告對(duì)代碼左側(cè)行列的遮擋,有時(shí)候這會(huì)擋住我們斷點(diǎn)調(diào)試時(shí)打的斷點(diǎn),需要的朋友可以參考下2023-05-05

