深入理解Java類加載機制(二)

1 前言

深入理解Java類加載機制(一)一文中,我們了解了類的加載和連接過程,這篇文章重點講述類的初始化過程,這樣,我們就將類的加載機制弄明白了。

2 初始化時機

在上一篇 類的加載時機5.2中我們提到了“首次主動使用”這個詞語,那什么是“主動使用”呢?
主動初始化的6種方式
(1)創建對象的實例:我們new對象的時候,會引發類的初始化,前提是這個類沒有被初始化。
(2)調用類的靜態屬性或者為靜態屬性賦值
(3)調用類的靜態方法
(4)通過class文件反射創建對象
(5)初始化一個類的子類:使用子類的時候先初始化父類
(6)java虛擬機啟動時被標記為啟動類的類:就是我們的main方法所在的類
只有上面6種情況才是主動使用,也只有上面六種情況的發生才會引發類的初始化。

同時我們需要注意下面幾個Tips:
1)在同一個類加載器下面只能初始化類一次,如果已經初始化了就不必要初始化了.
這里多說一點,為什么只初始化一次呢?因為我們上面講到過類加載的最終結果就是在堆中存有唯一一個Class對象,我們通過Class對象找到
類的相關信息。唯一一個Class對象說明了類只需要初始化一次即可,如果再次初始化就會出現多個Class對象,這樣和唯一相違背了。
2)在編譯的時候能確定下來的靜態變量(編譯常量),不會對類進行初始化;
3)在編譯時無法確定下來的靜態變量(運行時常量),會對類進行初始化;
4)如果這個類沒有被加載和連接的話,那就需要進行加載和連接
5)如果這個類有父類并且這個父類沒有被初始化,則先初始化父類.
6)如果類中存在初始化語句,依次執行初始化語句.

public class Test1 {
  public static void main(String args[]){
    System.out.println(FinalTest.x);
  }
}

class FinalTest{
  public static final int x =6/3;
  static {
      System.out.println("FinalTest static block");
  }
}

上面和下面的例子大家對比下,然后自己看看輸出的是什么?

public class Test2 {
    public static void main(String args[]){
        System.out.println(FinalTest2.x);
    }
}
class FinalTest2{

public static final int x =new Random().nextInt(100);
static {
    System.out.println("FinalTest2 static block");
}
}

第一個輸出的是
2
第二個輸出的是
FinalTest2 static block
61(隨機數)
為何會出現這樣的結果呢?
參考上面的Tips2和Tips3,第一個能夠在編譯時期確定的,叫做編譯常量;第二個是運行時才能確定下來的,叫做運行時常量。編譯常量不會引起類的初始化,而運行常量就會。

那么將第一個例子的final去掉之后呢?輸出又是什么呢?
這就是對類的首次主動使用,引用類的靜態變量,輸出的當然是:
FinalTest static block
2
那么在第一個例子的輸出語句下面添加
FinalTest.x =3;
又會輸出什么呢?
大家不妨試試!提示(Tips1)

3 類的初始化步驟

講到這里我們應該對類的加載-連接-初始化有一個全局概念了,那么接下來我們看看類具體初始化執行步驟。我們分兩種情況討論,一種是類有父類,一種是類沒有父類。(當然所有類的頂級父類都是Object)

沒有父類的情況:

1)類的靜態屬性
2)類的靜態代碼塊
3)類的非靜態屬性
4)類的非靜態代碼塊
5)構造方法

有父類的情況:

1)父類的靜態屬性
2)父類的靜態代碼塊
3)子類的靜態屬性
4)子類的靜態代碼塊
5)父類的非靜態屬性
6)父類的非靜態代碼塊
7)父類構造方法
8)子類非靜態屬性
9)子類非靜態代碼塊
10)子類構造方法

在這要說明下,靜態代碼塊和靜態屬性是等價的,他們是按照代碼順序執行的。
類的初始化內容這樣看起來還是挺多的,包括“主動使用”大家可以自己去寫一些demo去驗證一下。

4 結束JVM進程的幾種方式

了解完類加載機制之后,接下來我們了解一下結束JVM進程的幾種方式吧。

(1) 執行System.exit()
(2) 程序正常結束
(3) 程序拋出異常,一直向上拋出沒處理
(4) 操作系統異常,導致JVM退出

JVM有上面4種結束的方式,我們一一了解下:

(1)我們先來看看第一種方式,找到源代碼我們發現:

/**
     * Terminates the currently running Java Virtual Machine. The
     * argument serves as a status code; by convention, a nonzero status
     * code indicates abnormal termination.
     */
    public static void exit(int status) {
        Runtime.getRuntime().exit(status);
    }

上面的代碼解釋了System.exit()方法的作用就是:是中斷當前運行的java虛擬機。這是自殺方式。

(2)第二種程序正常結束的方式,我們在運行main方法的時候,運行狀態按鈕由綠色變紅色再變綠色的過程就是程序啟動-運行-結束的過程。 那么,我們來看看Android的程序,同樣,安卓也有自己的啟動方式,也是一個main方法。那么我們的android程序能夠一直運行的前提就是我們的main方法一直被執行著,一旦main方法執行完畢,程序就是kill。我們找找源代碼才能有更好的說服力;我們找到ActivityThread的main方法

 public static void main(String[] args) {
    SamplingProfilerIntegration.start();

    // CloseGuard defaults to true and can be quite spammy.  We
    // disable it here, but selectively enable it later (via
    // StrictMode) on debug builds, but using DropBox, not logs.
    CloseGuard.setEnabled(false);

    Environment.initForCurrentUser();

    // Set the reporter for event logging in libcore
    EventLogger.setReporter(new EventLoggingReporter());

    Security.addProvider(new AndroidKeyStoreProvider());

    Process.setArgV0("<pre-initialized>");

    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    AsyncTask.init();

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

上面的代碼都不用看,直接看最后兩行代碼。執行完Looper.loop()之后,直接拋出了異常。但是我們并沒有見到這個異常,說明我們的Looper一直在執行這樣保證我們的app不被kill掉。Android就是用這種方式來保證我們的app一直運行下去的。

(3)第三種方式不用過多解釋,一直沒有處理被拋出的異常,這樣導致了程序崩潰。
(4)第四種方式是系統異常導致了jvm退出。其實jvm就是一個軟件,如果我們的操作系統都出現了錯誤,那么運行在他上面的軟件(jvm)必然會被kill。

5 結束并回顧

到這里,我們基本都清楚了類的加載機制。那么我們在第一篇文章中開頭提到一個例子,我們這里來講講輸出的是什么,并且為何如此輸出,大家坐穩。

public class Singleton {
private static Singleton singleton = new Singleton();
public static int counter1;
public static int counter2 = 0;

private Singleton() {
    counter1++;
    counter2++;
}

public static Singleton getSingleton() {
    return singleton;
}

}

下面是我們的測試類TestSingleton

public class TestSingleton {
public static void main(String args[]){
    Singleton singleton = Singleton.getSingleton();
    System.out.println("counter1="+singleton.counter1);
    System.out.println("counter2="+singleton.counter2);

}
}

輸出是:
counter1=1
counter2=0
why?我們一步一步分析:

1 執行TestSingleton第一句的時候,因為我們沒有對Singleton類進行加載和連接,所以我們首先需要對它進行加載和連接操作。在連接階-準備階段,我們要講給靜態變量賦予默認初始值。
singleton =null
counter1 =0
counter2 =0
2 加載和連接完畢之后,我們再進行初始化工作。初始化工作是從上往下依次執行的,注意這個時候還沒有調用Singleton.getSingleton();
首先 singleton = new Singleton();這樣會執行構造方法內部邏輯,進行++;此時counter1=1,counter2 =1 ;
接下來再看第二個靜態屬性,我們并沒有對它進行初始化,所以它就沒辦法進行初始化工作了;
第三個屬性counter2我們初始化為0 ,而在初始化之前counter2=1,執行完counter2=0之后counter2=0了;

3 初始化完畢之后我們就要調用靜態方法Singleton.getSingleton(); 我們知道返回的singleton已經初始化了。
那么輸出的內容也就理所當然的是1和0了。這樣一步一步去理解程序執行過程是不是讓你清晰的認識了java虛擬機執行程序的邏輯呢。

那么我們接下來改變一下代碼順序,將
 public static int counter1;
 public static int counter2 = 0;
 private static Singleton singleton = new Singleton();
 又會輸出什么呢?為什么這樣輸出呢?
 這個問題留給大家去思考,主要還是理解為什么這樣輸出才是最重要的。

結合第一篇文章深入理解Java類加載機制(一),我們講完了類的加載機制。大家是否對java又有了不一樣的認識了呢?如果這2篇文章對你有幫助,請動動你的小指頭點個贊吧。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
禁止轉載,如需轉載請通過簡信或評論聯系作者。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容