1. 程式人生 > 其它 >Java:類載入(記憶體分析,初始化分析,類載入器的作用)

Java:類載入(記憶體分析,初始化分析,類載入器的作用)

類的載入

記憶體分析

Java記憶體

  • 堆:
    • 存放new的物件和陣列
    • 可以被所有執行緒共享,不會存放別的物件引用
  • 棧:
    • 存在基本變數型別(包含具體數字)
    • 引用物件的變數(存放引用在堆裡的具體地址)
  • 方法區
    • 包含所有的class和static變數
    • 可以被所有執行緒共享

類的載入過程

  1. 類的載入(Load):將class檔案位元組碼記憶體載入到記憶體中,並將這些靜態資料轉換成方法去的執行時資料結構,然後生成一個代表該類的java.lang.Class物件(該物件本就存在,我們使用時是進行獲取)
  2. 連結(Link):將Java類的二進位制程式碼合併到JVM執行狀態中的過程
    • 驗證:確保載入的類資訊符合JVM規範,沒用安全性問題
    • 準備:正式為類變數(static)分配記憶體並設定類變數預設初始值的階段,這些記憶體都將在方法去中進行分配
    • 解析:虛擬機器常量池內的符號引用(常量名)替換為直接引用(地址)的過程
  3. 初始化(Initialize):JVM對類進行初始化
    • 執行類構造器<clinit>()方法的過程。類構造器<clinit>()方法是由編譯期自動收集類中所有類變數的賦值動作和靜態程式碼塊中的語句合併產生的。(類構造用於構造類資訊)
    • 當初始化類的時候,如果發現其父類還沒進行初始化,則需先對其父類進行初始化
    • 虛擬機器會保證一個類的<clinit>()方法在多執行緒環境中被正確枷鎖和同步
public class Test05 {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(A.m);
    }
}

class A{
    static int m = 100;

    static {
        System.out.println("A類靜態程式碼塊初始化");
        m = 300;
    }

    public A() {
        System.out.println("A的無參構造初始化");
    }
}

測試結果為:m的結果由A類中靜態方法和靜態變數的順序決定。

A類靜態程式碼塊初始化
A的無參構造初始化
300

圖片參考

類的初始化分析

  • 類的主動引用(發生類的初始化
    • 虛擬機器啟動,初始化mian方法所在類
    • new新物件
    • 呼叫類的靜態成員(除final常量)和靜態方法
    • 使用java.lang.reflect包的方法對類進行反射呼叫
    • 初始化類,如果父類沒被初始化,會先初始化父類
  • 類的被動引用(不進行類的初始化
    • 訪問靜態域時,只有真正宣告該域的類才會被初始化(如子類引用父類的靜態變數,子類不初始化)
    • 通過陣列定義類引用
    • 引用常量(常量在連結階段就存入呼叫類的常量池中)

測試用例

類的定義:

class Parent{
    static {
        System.out.println("父類載入");
    }
    static int b = 0;
}

class Child extends Parent{
    static {
        System.out.println("子類載入");
        m = 300;
    }
    static int m = 100;
    static final int M = 1;
}
  1. 類的主動引用:
public class Test06 {
    static {
        System.out.println("Main類載入");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        //1. 主動引用
        Child child = new Child();

        //反射
        Class.forName("com.chachan53.class13annotation.reflection.Child");
    }
}

測試結果:

Main類載入
父類載入
子類載入
  1. 類被動引用
public class Test06 {
    static {
        System.out.println("Main類載入");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        //不產生類的引用
        System.out.println(Child.b);//Main類載入 父類載入 0
		//or
        Child[] array = new Child[3];//Main類載入
        //or
        System.out.println(Child.M);//Main類載入 1
    }
}

類載入器的作用

  • 作用:將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區的執行時資料結構,然後在隊中生成java.lang.Class物件,作為方法區中類資料的訪問入口
  • 類快取:標準的JavaSE類載入器可以按要求查詢類,但類被載入到類載入器中,將維持載入(快取)一段時間。JVM垃圾回收機制可以回收這些Class物件
  • 類載入器種類:
    • 引導類載入器:自帶,負責Java平臺核心庫(rt.jar),無法直接獲取
    • 擴充套件類載入器:負責jre/lib/ext目錄下的 jar包或-D java.ext.dirs指定目錄下的jar包裝入工作庫
    • 系統類載入器:複製java -classpath或 -D Java.class.path所指目錄下的類與jar包裝入工作,最常用
  • 雙親委派機制:建立新包時會檢測向上的類載入器是否有相同名稱的包,有該包無法使用
//獲取系統類的載入器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader); //sun.misc.Launcher$AppClassLoader@18b4aac2

//獲取系統載入器的父類載入器(擴充套件類載入器)
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent); //sun.misc.Launcher$ExtClassLoader@74a14482

//獲取擴充套件類載入器的父類載入器(根載入器,無法讀取)
ClassLoader parent1 = parent.getParent();
System.out.println(parent1); //null

//當前類的載入器:自定義類是使用者載入器載入的
ClassLoader classLoader = Class.forName("com.chachan53.class13annotation.reflection.Test07").getClassLoader();
System.out.println(classLoader); //sun.misc.Launcher$AppClassLoader@18b4aac2

//JDK內建類的載入器:根載入器,無法讀取
classLoader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader); //null

//如何獲得系統類載入器可以載入的路徑
System.out.println(System.getProperty("java.class.path"));

學習參考:狂神說Java反射和註解