切換語言為:簡體

Java物件的建立過程詳解

  • 爱糖宝
  • 2024-09-04
  • 2056
  • 0
  • 0

在Java中,物件建立是一個至關重要的過程,它涉及類載入、記憶體分配、初始化、賦值等多個階段。理解這些過程不僅有助於編寫更高效的程式碼,還能幫助我們最佳化程式的效能。本文將深入分析Java物件建立的全過程,重點討論類載入、物件初始化、靜態與非靜態初始化的順序,以及JVM堆疊記憶體的分配機制,並結合程式碼進行解析。

一、類載入過程

Java虛擬機器(JVM)在執行Java程式時,需要首先將類載入到記憶體中。類載入分為以下幾個步驟:

  1. 載入(Loading):JVM透過類載入器(ClassLoader)載入位元組碼檔案(.class檔案)到記憶體中,並將其轉換為 Class 物件。

  2. 連結(Linking)

    • 驗證(Verification):確保類的位元組碼符合JVM規範。

    • 準備(Preparation):為類的靜態變數分配記憶體,並初始化為預設值。

    • 解析(Resolution):將常量池中的符號引用替換為直接引用。

  3. 初始化(Initialization):執行類的靜態程式碼塊以及靜態變數的初始化。

示例程式碼:

public class InitializeDemo2 {  
    private static int k = 1;  
    private static int n = 10;  
    private static int i;  
    private String s = new String("-----");  
  
    static {  
        System.out.println("靜態程式碼塊:執行了");  
        System.out.println("k=" + k + ", n=" + n + ", i=" + i);  
    }  
  
    private int h = 2;  
  
    {  
        System.out.println("普通程式碼塊:執行了");  
        System.out.println(s);  
    }  
  
    public static void m1() {  
        System.out.println("靜態方法:執行了");  
        System.out.println("k=" + (k++) + ", n=" + n + ", i=" + i);  
    }  
  
    public void m2() {  
        System.out.println("普通方法:執行了");  
        System.out.println("k=" + (k++) + ", n=" + n + ", i=" + i);  
    }  
  
  
    public InitializeDemo2(String str) {  
        System.out.println("構造器:執行了");  
        System.out.println("k=" + (k++) + ", n=" + n + ", i=" + i);  
    }  
      
}

在上述程式碼中,InitializeDemo2 類的載入過程會按上述順序進行。靜態變數 k 在“準備”階段被分配記憶體並初始化為預設值 0,在“初始化”階段被賦值為 1

說到這裏,就要暫停下。我們怎麼驗證類的載入過程呢(本文僅從程式設計師更容易看懂的角度)?我們知道,如果呼叫類內部的靜態方法,會觸發類載入過程。

透過在另一個類呼叫IInitializeDemo2 的靜態方法:

public static void main(String args[]) {  
    InitializeDemo2.m1();  
}

可以驗證到輸出是:

靜態程式碼塊:執行了
k=1, n=10, i=0
靜態方法:執行了
k=1, n=10, i=0

說明,靜態變數在類載入階段就已經完成了!

二、物件初始化

在物件建立過程中,核心的過程是初始化,其分為兩個主要部分:靜態初始化非靜態初始化

  1. 靜態初始化:只在類第一次載入時執行,用於初始化靜態變數和執行靜態程式碼塊。

  2. 非靜態初始化:每次建立物件時執行,用於初始化例項變數和執行非靜態程式碼塊。

初始化順序:

  • 靜態變數和靜態程式碼塊:按它們在類中出現的順序執行,只在類載入時執行一次,在前面已經透過程式碼得到驗證。

  • 例項變數和例項程式碼塊:在每次建立物件時執行,執行順序同樣是按照它們在類中出現的順序。

  • 建構函式:例項變數和例項程式碼塊執行後,纔會執行建構函式。

示例程式碼:

public class InitOrder {
    static {
        System.out.println("Static Block 1");
    }
    
    static int x = print("Static Variable x");
    
    static {
        System.out.println("Static Block 2");
    }
    
    int y = print("Instance Variable y");
    
    {
        System.out.println("Instance Block");
    }
    
    public InitOrder() {
        System.out.println("Constructor");
    }
    
    static int print(String message) {
        System.out.println(message);
        return 0;
    }
    
    public static void main(String[] args) {
        new InitOrder();
    }
}

輸出結果:

Static Block 1
Static Variable x
Static Block 2
Instance Variable y
Instance Block
Constructor

從上面的例子中可以看出,靜態塊和靜態變數按照宣告順序初始化(讀者可以調整順序驗證),然後執行例項變數的初始化,接著執行例項塊,最後呼叫建構函式。

三、JVM堆疊記憶體的分配

JVM在建立物件時,主要使用堆記憶體、棧記憶體進行分配:

  1. 堆記憶體:用於儲存物件例項和陣列。每當我們用 new 關鍵字建立一個物件時,都會在堆中分配記憶體,其實這是一個動態的過程。

  2. 棧記憶體:用於儲存方法呼叫相關的資訊,包括方法的引數、區域性變數、運算元棧和返回地址。每次方法呼叫都會在棧中建立一個棧幀(Stack Frame)。

物件建立時的堆、棧記憶體分配過程:

  • new 關鍵字用於建立物件時,JVM首先會在堆中為該物件分配記憶體空間,並初始化預設值(如 int 預設值為 0,物件引用預設值為 null)。

  • 然後,JVM會將物件的引用地址儲存在棧中的區域性變數表中。

示例程式碼:

public class MemoryAllocation {
    public static void main(String[] args) {
        Example example = new Example();
    }
}

在這段程式碼中,new Example() 會在堆中分配 Example 物件的記憶體,並在棧中儲存指向這個物件的引用 example。當方法執行結束,棧幀被銷燬,但堆中的物件只要有引用指向它,就不會被垃圾回收。

對於記憶體分配,這裏本文不計劃深入探討了,計劃未來再寫篇文章。

四、賦值過程

在Java中,物件的賦值過程包括兩個主要部分:預設初始化顯式初始化

  • 預設初始化:在物件記憶體分配後,JVM自動將物件的所有例項變數設定為其預設值。

  • 顯式初始化:在預設初始化之後,Java按照程式碼中出現的順序進行顯式初始化。如果有建構函式,則在顯式初始化完成後執行建構函式中的賦值操作。

示例程式碼:

public class ValueAssignment {
    int i = 5;
    String s = "Hello";
    
    public ValueAssignment() {
        i = 10;
        s = "World";
    }
    
    public static void main(String[] args) {
        ValueAssignment va = new ValueAssignment();
        System.out.println("i: " + va.i + ", s: " + va.s);
    }
}

輸出結果:

i: 10, s: World

這裏的過程如下:

  1. 預設初始化i 被設定為 0s 被設定為 null

  2. 顯式初始化i 被賦值為 5s 被賦值為 "Hello"

  3. 建構函式初始化i 被重新賦值為 10s 被重新賦值為 "World"

結論

物件建立是Java程式中非常基礎但複雜的過程,涉及類載入、靜態與非靜態初始化的順序、記憶體分配和賦值等多個方面。理解這些細節對於編寫高效、健壯的Java程式碼至關重要。透過本文的分析和示例程式碼,我們深入探討了物件建立的每一個階段以及它們在JVM中的具體實現方式,為深入掌握Java的記憶體模型和物件生命週期奠定了基礎。

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.