java 復習整理(五 類加載機制與對象初始化)


類加載機制與對象初始化

 

一 . 類加載機制

  • 類加載機制是指.class文件加載到jvm並形成Class對象的機制。之后應用可對Class對象進行實例化並調用。類加載機制可在運行時動態加載外部的類,還可以達到類隔離的效果。

  • 類從而加載到虛擬機中開始,整個過程分為下圖七個階段,其中驗證,准備,解析統稱為解析。圖中加載,驗證,准備,初始化,卸載這5個階段的順序是確定的,類的加載過程必須按照這種過程按部就班的開始,而解析則不一定,它在某些情況下可能在初始化之后才開始,這是為了支持java語言的運行時綁定。

     

    1 . 裝載: 裝載過程負責找到二進制字節碼並加載至JVM--即負責查找和導入class文件。

    2 . 鏈接:

    (1) 檢驗:鏈接過程負責對二進制字節碼的格式進行檢驗,檢查class文件數據的正確性 (2) 准備:初始化裝載類中的靜態變量,給類的靜態變量分配存儲空間 (3) 解析:解析類中調用的接口,類,將符號引用轉為直接引用。

    3 . 初始化:初始化過程即執行類中的靜態初始化代碼,構造器代碼以及靜態屬性的初始化。以下四種情況會立即觸發初始化          

             (1) 調用了new,讀取或設置一個類的靜態字段的時候,或者調用一個類的靜態方法的時候。
             (2) 反射調用了類中的方法
    (3) 子類調用了初始化,如果子類初始化的時候發現父類尚未初始化,則會先出發父類的初始化。
    (4) JVM啟動過程中指定的初始化類(包含main方法的類)
    (5)當使用JDK 1.7的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,並且這個方法句柄對應的類沒有初始化,則需要先觸發其初始化.

  • JVM 的類加載通過ClassLoader及其子類來完成,分別是Bootstrap ClassLoader,Extension ClassLoader,App ClassLoader 以及用戶自定義的繼承自ClassLoader抽象類的實現Custom ClassLoader。

  • (1) Bootstrap ClassLoader : 此類並非ClassLoader的子類,在代碼中沒辦法拿到這個類的對象,Sun JDK會在啟動時自動加載此類。此加載器會將存放於<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath參數所指定的路徑中的,並且是虛擬機識別的(僅按照文件名識別,如 rt.jar 名字不符合的類庫即使放在lib目錄中也不會被加載)類庫加載到虛擬機內存中

    (2) Extension ClassLoader : 此加載器會將<JAVA_HOME>\lib\ext目錄下的,或者被java.ext.dirs系統變量所指定的路徑中的所有類庫加載.

    (3) App ClassLoader : 此加載器會將用戶類路徑(ClassPath)上所指定的類庫加載。

  • (4) Custom ClassLoader : 基於自定義的ClassLoader加載非classpath中的jar及目錄,還可以在加載之前對class文件做一些動作,例如加密。

  • JVM 的ClassLoader采用的是樹形結構,除 Bootstrap ClassLoader外都會有父級的ClassLoader。加載類時通常會按照樹形結構的原則進行。如果一個類加載器接收到了類加載的請求,它首先把這個請求委托給他的父類加載器去完成,每個層次的類加載器都是如此,因此所有的加載請求都應該傳送到頂層的啟動類加載器中,只有當父加載器反饋自己無法完成這個加載請求(它在搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載。 這樣做的好處是:java類隨着它的類加載器一起具備了一種帶有優先級的層次關系。例如類java.lang.Object,它存放在rt.jar中,無論哪個類加載器要加載這個類,最終都會委派給啟動類加載器進行加載,因此Object類在程序的各種類加載器環境中都是同一個類。相反,如果用戶自己寫了一個名為java.lang.Object的類,並放在程序的Classpath中,那系統中將會出現多個不同的Object類,java類型體系中最基礎的行為也無法保證,應用程序也會變得一片混亂。


二 . 初始化

java會盡量保證變量在使用前得到恰當的初始化,對於局部變量,如果未初始化會得到編譯時的錯誤,對於成員變量會自動初始化(0,null等)。
可以使用構造方法初始化成員變量,在運行時刻,可以調用方法或者執行某些動作來確定初值。但需要記住一點:無法阻止自動初始化的進行,它在構造方法被調用之前發生。對於所有基本類型和對象引用,包括在定義時已經指定初始值的變量,情況也是一樣的.例如如下實例代碼,i首先會被置為0,然后替換為指定的初始值7.

復制代碼
public class TestClass(){
    int i;//自動初始化賦值為0
    public static void main(String[] args){
        i = 7;//賦值為7
    }
}
復制代碼

 

1 . 初始化的順序:在類的內部,變量定義的先后順序決定了初始化的順序。即使變量的定義散布於方法定義之間,他們仍會在任何方法(包括構造方法)被調用之前得到初始化
對於如下代碼:w3這個引用會被初始化兩次,一次在調用構造方法之前,一次在調用期間,這時第一次引用的對象將被丟棄並被垃圾回收.

復制代碼
 public class House {
    Window w1 = new Window(1);
    public House() {
        System.out.println("House()");
        w3 = new Window(3_3);
    }
    Window w2 = new Window(2);
    public void method() {
        System.out.println("method()");
    }
    Window w3 = new Window(3);
    public static void main(String[] args) {
        House house = new House();
    }
}
class Window {
    Window(int order) {
        System.out.println("window" + order);
    }
} 
  //輸出結果為:
  window1
  window2
  window3
  House()
  window33
復制代碼

 

2 . 靜態數據的初始化
靜態初始化只有在必要的時候才進行,初始化的順序是先靜態對象(如果它們尚未因前面的的對象創建過程而被初始化),而后是“非靜態”對象。
示例代碼如下:

復制代碼
public class StaticInitailization {
    public static void main(String[] args) {
        System.out.println("creating new CupBoard() in main");
        new CupBoard();
        System.out.println("creating new CupBoard() in main");
        new CupBoard();
        table.f2(1);
        cupBoard.f3(1);
    }
    static Table table = new Table();
    static CupBoard cupBoard = new CupBoard();
}

class Bowl {
    public Bowl(int i) {
        System.out.println("Bowl(" + i + ")");
    }
    void f1(int i) {
        System.out.println("f1(" + i + ")");
    }
}

class Table {
    static Bowl bowl1 = new Bowl(1);
    Table() {
        System.out.println("Table()");
        bowl2.f1(1);
    }
    void f2(int i) {
        System.out.println("f2(" + i + ")");
    }
    static Bowl bowl2 = new Bowl(2);
}

class CupBoard {
    Bowl bowl3 = new Bowl(3);
    static Bowl bowl4 = new Bowl(4);
    CupBoard() {
        System.out.println("CupBoard()");
        bowl4.f1(2);
    }
    void f3(int i) {
        System.out.println("f3(" + i + ")");
    }
    static Bowl bowl5 = new Bowl(5);
}
// 結果如下:
Bowl(1)
Bowl(2)
Table()
f1(1)
Bowl(4)
Bowl(5)
Bowl(3)
CupBoard()
f1(2)
creating new CupBoard() in main
Bowl(3)
CupBoard()
f1(2)
creating new CupBoard() in main
Bowl(3)
CupBoard()
f1(2)
f2(1)
f3(1)
復制代碼

 

3 . 對象的創建過程: 假設有個名為Dog 的類

(1) 即使沒有顯示的使用static關鍵字,構造方法實際上也是靜態方法。因此,當首次創建Dog類的對象時,或者Dog類的靜態方法或者靜態變量被訪問時,java解釋器必須先查找類路徑,以定位Dog.class文件

(2)載入Dog.class文件(這將創建一個Class對象),有關靜態初始化的所有動作都會執行。因此,靜態初始化只在類首次加載的時候進行一次

(3)用new Dog 創建對象的時候,首先會在堆上為該對象分配足夠的存儲空間

(4)這塊存儲空間會被清零,這將會自動的為該對象的的所有基本數據類型賦初值

(5)執行所有出現於字段定義處的初始化動作。

(6)執行構造方法。

  • 復雜對象調用構造方法的順序:
    (1) 在其他任何事物發生之前,將分配給對象的的存儲空間初始化為二進制的零
    (2) 調用父類的構造方法
    (3) 按照聲明順序調用成員的初始化方法
    (4) 調用子類構造方法的主體部分

  • 編寫構方法注意避免調用其他方法,在構造方法內唯一能安全調用的方法是父類中的final方法(也適用於private方法,他們自動屬於final方法)。這些方法不能被override(覆寫)

參考書籍:分布式java應用基礎與實踐--林昊 3.1.2節 類加載機制
參考博客1:深入理解Java:類加載機制及反射
參考博客2:Java中普通代碼塊,構造代碼塊,靜態代碼塊區別及代碼示例


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM