想知道Java代碼執行順序么,Let's go

前言

YY:Kitty,我最近在看Thinking in Java 這本書
Kitty:喔?是么,你不是一直覺得那本書又厚又乏味,代碼還非常不用戶友好,難以閱讀,而總是停留在第一章么,這次不會還是停留在第一章吧(_ _
YY:好啦,這不人家感覺Java基礎還是需要打打扎實么,所以就只能硬著頭皮看這本被譽為Java屆圣經的神書咯!好不容易下定決心一定要認認真真看,你就別拿人家打趣兒了(捧臉害羞狀)
Kitty:好樣的嘛,那祝你收獲大大的咯!
YY:嘿嘿!昨天看了第五章--初始化和清理,才發現寫了那么久的Java,都還沒認真研究過從點擊“運行”那一刻開始,我們的代碼都是以怎樣的規則和順序一條一條被JVM執行的呢,你有想過這個問題么?
Kitty:呃呃。。。這個嘛,還真沒考慮過,那么你說說是怎么回事唄!
YY:好呀,正好我昨天寫了一個小Demo幫助我理解,那么我就用這個小Demo和你講講,順便鞏固一下我的知識吧,要是有講錯的或者講得不好的地方,你可得幫我指出來哈,不然我還糊里糊涂的以為自己都理解對了呢!
Kitty:好的好的,木有問題(親愛的讀者朋友們,你們要是發現文中有不足之處,一定一定要給小編我指出來哈,在此先行謝過了_
YY:先上小Demo

<a name="demo">小Demo</a>

// 父類
public class Father {
    // 非靜態變量
    C fa = new C("fa");
    
    // 靜態變量
    static C fb = new C("fb");

    // 靜態語句塊
    static {
        OutUtil.print("Static blocks 1 in Father! ");
    }

    // 靜態語句塊
    static {
        OutUtil.print("Static blocks 2 in Father! ");
    }
    
    // 靜態常量
    static final int T = 28;

    // 構造方法
    public Father() {
        super();
        OutUtil.print("Construct method in Father! ");
    }

    // 帶參構造器
    public Father(String name) {
        OutUtil.print("Construct method in Father! " + "Name = " + name);
    }

    // 非靜態代碼塊
    {
        OutUtil.print("Common blocks in Father! ");
    }

    // 靜態方法
    static void staticShow() {
        OutUtil.print("Static method in Father! ");
    }

    // 非靜態方法
    void show() {
        OutUtil.print("Common method in Father! ");
    }

}
// 子類
public class Child extends Father {

    // 非靜態變量
    C ca = new C("ca");

    // 靜態常量
    static final int T = 28;

    // 構造方法
    public Child() {
        super();
        OutUtil.print("Construct method in Child! ");
    }

    // 帶參構造器
    public Child(String name) {
        OutUtil.print("Construct method in Child! " + "Name = " + name);
    }

    // 非靜態代碼塊
    {
        OutUtil.print("Common blocks in Child! ");
    }

    // 靜態方法
    static void staticShow() {
        OutUtil.print("Static method in Child! ");
    }

    // 非靜態方法
    void show() {
        OutUtil.print("Common method in Child! ");
    }

    // 靜態變量
    static C cb = new C("cb");

    // 靜態語句塊
    static {
        OutUtil.print("Static blocks 1 in Child! ");
    }

    // 靜態語句塊
    static {
        OutUtil.print("Static blocks 2 in Child! ");
    }

}
// 輔助類
public class C {
    public static final String A = "A in C";
    
    public static String showC() {
        return "showC method in C!";
    }
    
    public C(){
        OutUtil.print("Construct method in C!");
    }
    
    public C(String msg){
        OutUtil.print("Construct method in C! " + msg);
    }
}
// 入口程序所在類
public class Main {
    
    C ma = new C("ma");     // 打印結果顯示ma并未進行初始化
    static C mb = new C("mb");  
    
    public Main(){
        OutUtil.print("I am Main!");
    }
    
    static{ 
        OutUtil.print(mb.getClass().getCanonicalName());
    }
    
    public static void main(String[] args) {
        OutUtil.print("Main");
        Child child = new Child();
        child.show();
        OutUtil.print(C.A);
        OutUtil.print(C.showC());
    }
    
    static Child mc = new Child("mc");
}
// 打印輸出的工具類
public class OutUtil {
     public static void print(Object o) {
        System.out.println(o);
    }
}
// 程序運行輸出結果
Construct method in C! mb
classloadertest.C
Construct method in C! fb
Static blocks 1 in Father! 
Static blocks 2 in Father! 
Construct method in C! cb
Static blocks 1 in Child! 
Static blocks 2 in Child! 
Construct method in C! fa
Common blocks in Father! 
Construct method in Father! 
Construct method in C! ca
Common blocks in Child! 
Construct method in Child! Name = cb
Main
Construct method in C! fa
Common blocks in Father! 
Construct method in Father! 
Construct method in C! ca
Common blocks in Child! 
Construct method in Child! 
Common method in Child! 
A in C
showC method in C!

Kitty:控制臺輸出了好多語句呀,看得我眼睛都花了,你給我指點指點撒
YY:OK,先給你畫個圖吧!


結果分析.PNG

YY:看了上面的圖應該可以大致看出哪些語句被執行了,哪些語句沒有被執行,下面再給你分析分析。

Demo分析

上面的小Demo中含有四個類:Father、Child、C和Main,其中Child類繼承自Father類;C類是一個輔助類,主要用于打印輸出,便于分析程序的運行流程;Main類是Java程序入口方法main方法所在的類,也是Java程序運行時加載的第一個類。

下面根據上圖分析該Demo的運行流程。從圖中可以看出,程序首先執行了Main類中的靜態域和靜態代碼塊(靜態域和靜態代碼塊的具體執行順序依代碼中定義的順序而定),然后執行main方法中的代碼,根據main方法中的代碼需要,再加載所需要的類并進行初始化。

注:

  1. 整個程序執行過程中并沒有執行Main類中的非靜態域,也沒有執行Main類的初始化方法,由此可知,并沒有實例化Main類;
  2. 圖中第一個大矩形框執行的是Main類最后一行語句,即初始化靜態域mc。從矩形框中可以看出創建一個類實例的流程:
    a. 首先執行父類中的靜態域和靜態代碼塊(靜態域和靜態代碼塊的具體執行順序依代碼中定義的順序而定);
    b. 然后執行子類中的靜態域和靜態代碼塊;
    c. 然后執行父類中的非靜態域和非靜態代碼塊;
    d. 接著執行父類的初始化方法(如果子類在構造函數中明確指明了調用父類的哪一個構造函數,則調用相應的構造函數,否則調用父類的無參構造函數)
    e. 然后執行子類中的非靜態域和非靜態代碼塊;
    d. 最后執行子類的初始化方法(即被調用的那一個構造函數)。
  3. static Child mc = new Child("mc")和Child child = new Child()這兩句都是實例化一個Child對象,從下圖可以看出,類中的靜態域只進行了一次初始化操作,而非靜態域則進行了兩次初始化,由此可知,一個類中的靜態域和靜態代碼塊只會在類第一次加載時進行初始化,而非靜態域和非靜態代碼塊則會在每一次實例化時均執行。
對象實例化(1)

Kitty:那也可能是因為上面的代碼中第一次是初始化一個靜態實例,第二次只是初始化一個非靜態實例呀?
YY:問得很好,要解開這個疑惑很簡單,我們變一下程序流程就好了。

public class Main {
    
    C ma = new C("ma");
    static C mb = new C("mb");
    
    public Main(){
        OutUtil.print("I am CallbackMain!");
    }
    
    static{ 
        OutUtil.print(mb.getClass().getName());
    }
    
    public static void main(String[] args) {
        OutUtil.print("Main");
        Child mc = new Child("mc");
        Child child = new Child();
        child.show();
        OutUtil.print(C.A);
        OutUtil.print(C.showC());
    }
    
//  static Child mc = new Child("mc");
}
對象實例化(2)

YY:上面的代碼和圖顯示了同時在main方法中實例化兩個Child對象的執行流程,可以看到
上面的第3點觀察結論是正確的--類中的靜態域只會在類第一次加載時執行,而非靜態域則會在每一次實例化時均執行。
Kitty:確實是這樣的呀,看來!

YY:下面用一個流程圖簡易表示Java中對象的初始化順序以加深記憶吧!Java中,沒有顯式使用extends繼承的類都默認繼承自Object類,也就是說,除Object類以外,每個類都會有一個顯式或隱式的父類,并且任何對象的初始化都會自Object類開始。

對象初始化順序

Kitty:好啦,通過你的小Demo演示以及你的觀察結論,我已經大致清楚了Java程序的執行流程以及在Java中,創建一個對象會經歷哪些過程了。可是。。。
YY:可是什么???
Kitty:你不說還好,被你這么一說吧,我的腦子里冒出了一堆???
YY:哈哈,有問號說明你還在思考,說說都有哪些疑問,我們一起把它們變成!!!唄
Kitty:我現在主要有一下幾個困惑:

  1. 為什么一個對象的初始化過程是這樣的呢?
  2. 上面的Demo顯示Father類和Child類會執行非靜態域、非靜態初始化塊和構造方法,但是Main類中的非靜態域與初始化方法卻并未執行,這又是怎么一回事呢?
  3. 那么,Java中一個類的初始化過程又是怎樣的呢,和對象的初始化過程又有什么區別和聯系?
  4. static關鍵字好神奇的樣子,可是該怎么用呢?

YY:問題已經拋出來了,接下來就開始解決它們。

預備知識

YY:看樣子你的Java基礎也不咋地呀,那么先給你介紹幾個基本概念作為預備知識熱熱身吧,不然后面你又該冒???了(_

  • 普通代碼塊:在方法或語句中出現的 {} 就稱為普通代碼塊,普通代碼塊和一般的語句執行順序由他們在代碼中出現的次序決定--“先出現先執行”

  • 構造塊:直接在類中定義且沒有加static關鍵字的代碼塊 {} 稱為構造代碼塊(構造塊)。用來初始化每一個對象的非靜態變量,構造塊在每次創建 **對象 **時均會被調用,并且 **構造塊的執行次序優先于類構造函數 **;

  • 靜態代碼塊: 在 Java 中使用 static 關鍵字聲明的代碼塊 {} 稱為靜態代碼塊(靜態初始塊)。靜態塊用于初始化類,為類的屬性初始化。每個靜態代碼塊只會在Class對象首次加載時執行一次。由于JVM在加載類時會執行靜態代碼塊,所以靜態代碼塊先于主方法執行。如果類中包含多個靜態代碼塊,那么將按照 **"先出現先執行" **的順序執行。

    • 靜態代碼塊不能存在于任何方法體內
    • 靜態代碼塊不能直接訪問非靜態實例變量和實例方法,需要通過類的實例對象來訪問
    • 即便沒有顯式使用使用static關鍵字,構造器實質上也是靜態方法(出自Thinking in Java)
  • JVM中的內存區域: Java 程序執行時需要先被 JVM 加載進內存,為了提高程序運算效率, JVM 會將不同類型的數據加載進內存中的不同區域,因為每一片區域均有不同的內存管理方式和數據處理方式!JVM 中幾個比較重要的區域為:

    • 程序計數器:每個線程擁有一個PC寄存器,在線程創建時創建,指向下一條指令的地址,執行本地方法時,PC的值為undefined
    • 方法區:保存裝載的類信息,如類型的常量池、字段、方法信息、方法字節碼等,通常和永久區(Perm)關聯在一起
    • 堆區:用于存放類的對象實例,為所有線程所共享
    • 棧區: 也叫java虛擬機棧,是由一個一個的棧幀組成的后進先出的棧式結構,棧楨中存放方法運行時產生的局部變量、方法出口等信息。當調用一個方法時,虛擬機棧中就會創建一個棧幀存放這些數據,當方法調用完成時,棧幀消失,如果方法中調用了其他方法,則繼續在棧頂創建新的棧楨。棧區是 線程私有 的,生命周期和線程相同

YY:熱身活動完成,進入正題!

Java類的生命周期

Java類生命周期

當我們編寫一個java的源文件后,經過編譯會生成一個后綴名為 **.class **的文件,這種文件叫做字節碼文件,只有這種字節碼文件才能夠在 Java 虛擬機中運行, **Java 類的生命周期就是指一個class文件從加載到卸載的全過程 **,如上圖所示。

  1. 裝載:在裝載階段,JVM會通過一個類的全限定名獲取描述此類的.class文件,然后通過這個.class文件將類的信息加載到 JVM 的方法區,然后在堆區中實例化一個java.lang.Class對象,作為方法區中這個類的信息的入口。虛擬機設計團隊把加載動作放到 JVM 外部實現,以便讓應用程序決定如何獲取所需的類,實現這個動作的代碼被稱為 “類加載器” 。至于何時加載一個類, JVM 并沒有一個統一的規范,所以不同的虛擬機可能采取不同的加載策略,有些虛擬機會選擇 在執行前就預先加載類 ,而另一些虛擬機則會在 真正需要使用到一個類的時候才會加載 。但無論如何,一個類總是會在 JVM “預期”到即將會使用之前被加載。常用的hotspot虛擬機采取的是懶加載原則,即等到真正需要使用到一個類時才加載這個類;
  2. 連接: JVM 將已讀入的二進制文件合并到 JVM 的運行時狀態的過程,這個過程由 驗證、準備和解析 三個子步驟構成
  1. 驗證:確認該類型符合 Java 語言的語義,并且該類型不會危及 JVM 的完整性,主要包括 格式驗證、元數據驗證、字節碼驗證和符號引用驗證 等;
  2. 準備:在準備階段,JVM 為 類變量 (所謂類變量就是被 static 關鍵字修飾的變量)分配內存,設置默認的初始值,(默認值的設置過程是通過將此片內存區清零實現的,即通過將對象內存設為二進制零值而一舉生成)此時默認值設置如下,而非在代碼中賦予的值(在準備階段并不會執行 Java 代碼):
  • 基本類型(int、long、short、char、byte、boolean、float、double): 默認值為0;
  • 引用類型: 默認值為 null;
  • 常量: 默認值為程序中設定的值,比如我們在程序中定義final static int a = 8,則準備階段中a的初值就是8;
private static int a = 8;  
// 上面這句在準備階段只會將 a 初始化為0,需要等到后面的初始化階段,才會將 a 賦值為8
private static final int A = 8;
// 這句例外,因為上面這句表明 A 是一個編譯期常量
// 所以在編譯階段會為 A 生成 ConstantValue 屬性,在準備階段虛擬機會根據 ConstantValue 屬性將 A 賦值為8
  1. 解析: 在類的常量池中尋找類、接口、方法和字段的符號引用,將符號引用替換為直接引用的過程,實質上,在符號引用被程序首次使用以前,這個過程都是可選的。
    • 符號引用:使用一組符號來描述所引用的目標,可以是任何形式的字面常量,定義在Class文件格式中
    • 直接引用:可以是直接指向目標的指針、相對偏移量或能間接定位到目標的句柄
  1. 初始化:即為 **類變量 **賦予 **“正確” **的初始值的過程,(“正確”的初始值是指代碼中希望這個類變量擁有的初始值)也就是上面的小Demo中的執行靜態域和靜態代碼塊的過程(后面詳述類變量初始化過程);

  2. 對象生命:這個就很好理解了,一旦一個類完成了裝載、連接和初始化這三個過程,這個類就隨時可以被使用了,包括調用類變量、類方法以及實例化類等。每次對類進行實例化操作時都會創建該類的一個新的對象,開啟該對象的生命周期。對象的生命周期包含三個階段:

1).*** 對象實例化:*** 即對象的初始化階段,在本階段完成對象初始化工作,回看上面的Demo,即執行類中的非靜態域和非靜態代碼塊部分,然后執行類的構造函數中的代碼。具體流程為:當通過顯式或隱式的方式創建一個類的實例時,JVM 會首先為該類及其所有超類中的實例變量在堆中分配內存,然后 JVM 會將該塊內存空間清零,從而將實例變量初始化為 默認的初始值 (數字、字符和布爾型變量初始化為0,引用類型變量初始化為null),然后根據我們在代碼中書寫的內容,為實例變量賦予正確的初始值;完成對象實例化過程后,就可以通過該對象的引用使用對象了,如調用對象的方法、獲取對象中某個域的值等;

2). *** 垃圾收集:*** 當一個對象不再被引用的時候,JVM 就可以將這個對象所占據的內存回收,從而使得該部分內存可以被再次使用,垃圾回收時機、策略等是一個非常復雜的過程,具體可以參見深入Java虛擬機一書;

3). 對象終結:當一個對象被垃圾收集器收集后,該對象就不復存在,也就是說該對象的生命周期結束;

  1. 類卸載:類卸載是類生命周期的最后一個過程,當程序不再引用某一類型時,那么這個類型就無法再對未來的計算過程產生影響,從而該類就可以被 JVM 垃圾回收。

YY:上面大致描述了 Java 中一個類的生命周期流程,本文并不會對每個過程都進行深入細致的分析,那樣的話會陷入到細節陷阱中無法自拔,如果你對哪個部分有疑惑或者感興趣的話,可以去查閱相關資料詳加了解,下面詳細講解一下 Java 類的初始化過程,畢竟這才是本文的重點嘛_
Kitty:好的,好的!我現在正興趣濃厚,快開始講吧!
YY:瞧你那猴急猴急的樣,平時也沒見你這么認真學習了!那我開始咯,你好好聽哈。

Java類初始化

為了讓一個類/接口被首次主動使用,在加載、連接過程完成后,JVM 會執行類初始化過程。前面已經簡要介紹過,類初始化時會執行類變量初始化語句和靜態語句塊,將準備階段賦予類變量的默認初始值替換為“正確”的初始化值。

Kitty:** 首次 **我知道,不就是第一次么,可是 主動使用 是個什么鬼?
YY:別急撒,接下來就為你揭開它的神秘面紗

主動使用 VS 被動使用

  • 主動使用:在 Java 中只有如下幾種活動被視為主動使用
    • 創建類的新實例;
    • 調用類中聲明的靜態方法;
    • 操作類/接口中聲明的 非常量 靜態域;
    • 調用 Java 中的反射方法;
    • 初始化一個類的子類;
    • 指定一個類作為 JVM 啟動時的初始化類,即 main 函數所在的類。
  • 被動使用:不屬于上述六種情況的活動均被視之為被動使用

** 注:被動使用一個類時并不會觸發類初始化過程**, 如

  • 非首次主動使用一個類時不會觸發類的初始化過程,也就是所第二次主動使用一個類也不再會觸發類的初始化
  • 使用一個類的非常量靜態字段時,只有當該字段確實是由當前類/接口聲明的時才可以稱之為主動使用,否則是被動使用。比如說,當通過子類調用父類的 public 的非常量靜態域時,對于子類來說這是被動使用,對于父類才是主動使用,所以會觸發父類的初始化,而不會觸發子類的初始化
  • 如果一個變量被 static 和 final 同時修飾,并且使用一個編譯期常量表達式進行初始化,那么對這樣的字段的使用就不是對聲明該字段的類的主動使用,因為 Java編譯器會把這樣的字段解析成對常量的本地拷貝(該常量存在于引用者的字節碼流中或者常量池中,或二者均有)
  • 定義一個類的對象數組時并不會觸發該類的初始化

Kitty:哎呀,一會兒是主動使用,一會兒又不是主動使用的,這些抽象的概念一點都不好理解,我的腦子里現在就像小燕子說的了--全是漿糊了(衰)
YY:抽象的東西確實不太好理解和記憶,下面沿用上面小 Demo 中出現的類寫幾個小例子,你就會有所理解了。

示例說明

  • 非首次主動使用一個類時也不會觸發類的初始化過程
public class InitTest {
    public static void main(String[] args) {
        Father.staticShow();    //首次主動使用
        Father.staticShow();   //非首次主動使用
    }
}

程序輸出為:

Construct method in C! fb
Static blocks 1 in Father! 
Static blocks 2 in Father! 
Static method in Father! 
Static method in Father! 

由輸出可知,雖然在代碼中先后兩次執行了Father中的靜態方法staticShow(),但是Father類的初始化過程只執行了一次。

  • 通過子類調用父類的靜態域
public class InitTest {
    public static void main(String[] args) {
        OutUtil.print(Child.str);  // str是Father類中的靜態字符串,初始值為“str in Father”
    }
}

程序輸出為:

Construct method in C! fb
Static blocks 1 in Father! 
Static blocks 2 in Father! 
str in Father

輸出顯示,只執行了Father類的初始化代碼,而未執行Child類的初始化代碼。

  • 調用編譯期常量不會觸發類初始化
public class InitTest {
    public static void main(String[] args) {
        OutUtil.print(Father.T);
    }
}

程序輸出為:

28

由此可見并未觸發類Father的初始化操作。

  • 定義一個類的對象數組時并不會觸發該類的初始化
public class InitTest {
    public static void main(String[] args) {
        Father[] cArray = new Father[8];
    }
}

執行上面這段代碼后,控制臺并未產生輸出,這就說明并未初始化Father類。可以通過查看這段代碼產生的字節碼文件加以驗證。

類字節碼

由上圖可以看到,首先執行了Object類的初始化方法,然后執行InitTest類中的main方法,其中anewarray指令為新數組分配空間,但并未觸發類Father的初始化。

Kitty:喔!!!原來主動使用和被動使用是這樣的呀,Java 加載類,對類進行初始化的時機為首次主動使用的時候,可是你還是沒有講 JVM 執行類初始化操作的具體流程呀。
YY:OK,接下來就是了。

如何執行類的初始化操作?

在 Java 類和接口的 class 文件中有一個只能夠被 JVM 調用的<clinit>()方法,這個方法會將類/接口的所有類變量初始化語句和靜態初始化塊均收集起來,然后在需要執行類初始化操作時,JVM 便調用該方法為類變量賦予“正確”的初始值。具體由以下兩個步驟構成:

  1. 如果類存在直接超類,并且直接超類還未被初始化,則先初始化超類;
  2. 如果類存在類初始化方法,則執行該初始化方法;
    在執行超類的初始化時也是通過這兩個步驟完成,因此,程序中第一個被初始化的類永遠是Object類,其次是被主動使用的類繼承層次樹上的所有類,超類總是先于子類被初始化。

注:

  • 初始化接口時并不需要先初始化其父接口,只有使用父接口中定義的變量時,才會執行父接口的初始化動作
  • <clinit>()方法的代碼并不會顯式調用超類的<clinit>()方法,JVM 在調用類的<clinit>()方法時會先確認超類的<clinit>()方法已經被正確調用
  • 為了防止多次執行<clinit>,JVM 會確保<clinit>()方法在多線程環境下被正確的加鎖同步執行。當有多個線程需要對一個類執行初始化操作時,只能由一個線程來執行,其它線程均處于等待狀態,當活動線程執行完成后,必須通知其它線程
  • 并非所有的類均會在class文件中擁有<clinit>()方法,只有那些的確需要執行 Java 代碼來賦予類變量正確的初始值的類才會有<clinit>()方法。下面幾種情況下,類的class文件中不會包含<clinit>()方法:
    • 類中沒有聲明任何類變量,也沒有包含靜態初始化塊;
public class Test1 {
    int a = 8;
    int add(){return ++a;}
}
  • 雖然類聲明了類變量,但是并沒有明確使用類變量初始化語句或靜態初始化語句對它們進行初始化;
public class Test2 {
    static int c;
}
  • 類中僅包含static final變量的初始化語句,并且初始化語句是編譯期常量表達式
public class Test3 {
    static final int A = 8;
    static final int B = A * 8;
}

上面代碼中,A和B均是編譯時常量,JVM 在加載 test 類時,不會將A、B作為類變量保存到方法區,而是會被當做常量,被 Java 編譯器特殊處理。因此,不需要<clinit>()方法來對它們進行初始化。

YY:OK,到此 Java 類的初始化部分就結束啦,下面由Kitty你來說說你的理解,然后再回顧一下上面的小 Demo唄。
Kitty:好的,正好回顧一下,不然知識都是零零散散的,一下就忘了。

Demo回顧

下面將 Java 程序入口類 Main 的代碼提出來了,其余代碼見小Demo

// 入口程序所在類
public class Main {

    C ma = new C("ma");        // 打印結果顯示ma并未進行初始化
    static C mb = new C("mb");    

    public Main(){
        OutUtil.print("I am Main!");
    }

    static{    
        OutUtil.print(mb.getClass().getCanonicalName());
    }

    public static void main(String[] args) {
        OutUtil.print("Main");
        Child child = new Child();
        child.show();
        OutUtil.print(C.A);
        OutUtil.print(C.showC());
    }

    static Child mc = new Child("mc");
}

當前程序運行流程:

  1. main 方法所在的類為 Main, 所以 JVM 會先加載 Main類;
  2. 完成 Main 類的連接步驟,將 Main 類中的靜態域 mb 和 mc 初始化為null;
  3. 初始化 Main 類,執行 Main 類中的靜態塊,此時會執行如下幾句:
static C mb = new C("mb");
static{ 
        OutUtil.print(mb.getClass().getName());
    }
static Child mc = new Child("mc");
  1. 執行 main 方法

static Child mc = new Child("mc")一句可以更加詳細地說明類初始化流程。

  1. 首先,JVM 會加載該句所需要的類,因為 Child 類是 Father 類的子類,所以首先加載 Father 類;
  2. 連接 Father 類;
  3. 初始化 Father 類(即會執行 Father 類中的靜態塊和靜態域初始化語句);
  4. 加載 Child 類;
  5. 連接 Child 類;
  6. 初始化 Child 類;
  7. 執行 Father 類的非靜態域初始化語句和構造塊;
  8. 執行 Father 類的構造方法;
  9. 執行 Child 類的非靜態域初始化語句和構造塊;
  10. 執行 Child 類的構造方法。

總結

  1. 只有在應用程序 首次主動使用 一個類時,JVM 才會對這個類進行初始化;
  2. 類的生命周期主要有如下幾個階段:加載--連接--初始化--[對象生命]--卸載,其中對象生命階段是可選的,也就是說,一旦完成類的加載、連接和初始化工作,就可以使用類了,當程序中不再有該類的引用時,就可以被 JVM 回收,至此類生命周期結束;
  3. 對象的生命周期為:初始化--使用--回收--終結,對象的生命周期依賴于類的生命周期,只有當完成了類的加載、連接和初始化工作后,才會創建對象;
  4. Java 類在進行初始化時,會先執行父類的初始化步驟,再執行子類的初始化,所以所有的類初始化工作均起始于 Object 類

YY:Well done!
Kitty:嘿嘿,疑惑消除,可以愉快地玩耍了~~~

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

推薦閱讀更多精彩內容

  • 父類 static 塊 1 執行 父類 靜態成員staticSam1初始化 父類 靜態成員staticSam2初始...
    YCix閱讀 1,335評論 0 0
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,737評論 18 399
  • 一:java概述:1,JDK:Java Development Kit,java的開發和運行環境,java的開發工...
    ZaneInTheSun閱讀 2,686評論 0 11
  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,339評論 11 349
  • 如果人生有悔 疏忽了, 千里之外, 母親每天對兒的絮念, 只說自己還年輕。 不理解, 家里門外, 母親見面的嘮叨,...
    關于你的我的閱讀 315評論 0 0