1. 程式人生 > 其它 >Java註解和反射03:類載入

Java註解和反射03:類載入

Java註解和反射03:類載入

Java類載入記憶體分析

類的載入過程

當程式主動使用某個類時,如果該類還未被載入到記憶體中,則系統會通過如下三個步驟來對該類進行初始化。

類的載入與ClassLoader的理解

  • 載入:將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區的執行時資料結構,然後生成一個代表這個類的java.lang.Class物件
  • 連結:將Java類的二進位制程式碼合併到JVM的執行狀態之中的過程
    • 驗證:確保載入的類資訊符合JVM規範,沒有安全方面的問題
    • 準備:正式為類變數(static)分配記憶體並設定類變數預設初始值的階段,這些記憶體都將在方法區進行分配
    • 解析:虛擬機器常量池內的符號引用(常量名)替換為直接引用(地址)的過程
  • 初始化:
    • 執行類構造器()方法的過程。類構造器()方法是由編譯期自動收集類中所有類變數的賦值動作和靜態程式碼塊中的語句合併產生的。(類構造器是構造類資訊的,不是構造該類物件的構造器)
    • 當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化
    • 虛擬機器會保證一個類的()方法在多執行緒環境中被正確加鎖和同步
package com.lurenj.reflection;

public class Test05 {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(A.m);

        /*
        類構造器<clinit>()方法進行合併
        1.載入到記憶體,會產生一個類對應的Class物件
        2.連結,連結結束後m = 0
        3.初始化
            <clinit>(){
                    System.out.println("A類靜態程式碼塊初始化");
                    m = 300;
                    m = 100;
                    System.out.println("A類的無參構造初始化");
            }
         */

    }
}

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

    static int m = 100;

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

什麼時候會發生類初始化

  • 類的主動引用(一定會發生類的初始化)
    • 當虛擬機器啟動,先初始化main方法所在的類
    • new一個類的物件
    • 呼叫類的靜態成員(除了final常量)和靜態方法
    • 使用java.lang.reflect包的方法對類進行反射呼叫
    • 當初始化一個類,如果其父類沒有被初始化,則先會初始化它的父類
  • 類的被動引用(不會發生類的初始化)
    • 當訪問一個靜態域時,只有真正宣告這個域的類才會被初始化。如:當通過子類引用父類的靜態變數,不會導致子類初始化
    • 通過陣列定義類引用,不會觸發此類的初始化
    • 引用常量不會觸發此類的初始化(常量在連結階段就存入呼叫類的常量池中了)
package com.lurenj.reflection;

//測試類什麼時候會初始化
public class Test06 {
    static {
        System.out.println("Main類被載入");
    }

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

        //反射也會產生主動引用
        Class.forName("com.lurenj.reflection.Son");
        //不會產生類的引用的方法
        System.out.println(Son.b);

        Son[] array = new Son[5];

        System.out.println(Son.M);
    }
}

class Father{

    static int b = 2;

    static {
        System.out.println("父類被載入");
    }

}

class Son extends Father{
    static {
        System.out.println("子類被載入");
        m = 300;
    }
    static int m = 100;
    static final int M = 1;
}

類載入器的作用

  • 類載入的作用:將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區的執行時資料結構,然後在堆中生成一個代表這個類的java.lang.Class物件,作為方法區中類資料訪問入口
  • 類快取:標準的JavaSE類載入器可以按要求查詢類,但一旦某個類被載入到類載入器中,它將維持載入(快取)一段時間。不過JVM垃圾回收機制可以回收這些Class物件

類載入器作用是用來把類(class)裝載進記憶體的。JVM規範定義瞭如下型別的類的載入器

package com.lurenj.reflection;

public class Test07 {
    public static void main(String[] args) throws ClassNotFoundException {

        //獲取系統類的載入器
        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@1b6d3586

        //獲取擴充套件類載入器的父類載入器-->根載入器(C/C++編寫)
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);//null

        //測試當前類是哪個載入器載入的
        ClassLoader classLoader = Class.forName("com.lurenj.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"));
        /*
        D:\Environment\java\jdk1.8\jre\lib\charsets.jar;
        D:\Environment\java\jdk1.8\jre\lib\deploy.jar;
        D:\Environment\java\jdk1.8\jre\lib\ext\access-bridge-64.jar;
        D:\Environment\java\jdk1.8\jre\lib\ext\cldrdata.jar;
        D:\Environment\java\jdk1.8\jre\lib\ext\dnsns.jar;
        D:\Environment\java\jdk1.8\jre\lib\ext\jaccess.jar;
        D:\Environment\java\jdk1.8\jre\lib\ext\jfxrt.jar;
        D:\Environment\java\jdk1.8\jre\lib\ext\localedata.jar;
        D:\Environment\java\jdk1.8\jre\lib\ext\nashorn.jar;
        D:\Environment\java\jdk1.8\jre\lib\ext\sunec.jar;
        D:\Environment\java\jdk1.8\jre\lib\ext\sunjce_provider.jar;
        D:\Environment\java\jdk1.8\jre\lib\ext\sunmscapi.jar;
        D:\Environment\java\jdk1.8\jre\lib\ext\sunpkcs11.jar;
        D:\Environment\java\jdk1.8\jre\lib\ext\zipfs.jar;
        D:\Environment\java\jdk1.8\jre\lib\javaws.jar;
        D:\Environment\java\jdk1.8\jre\lib\jce.jar;
        D:\Environment\java\jdk1.8\jre\lib\jfr.jar;
        D:\Environment\java\jdk1.8\jre\lib\jfxswt.jar;
        D:\Environment\java\jdk1.8\jre\lib\jsse.jar;
        D:\Environment\java\jdk1.8\jre\lib\management-agent.jar;
        D:\Environment\java\jdk1.8\jre\lib\plugin.jar;
        D:\Environment\java\jdk1.8\jre\lib\resources.jar;
        D:\Environment\java\jdk1.8\jre\lib\rt.jar;
        D:\Note\net\study\out\production\study;
        D:\Program Files\Intelij IDEA\IntelliJ IDEA 2020.1.2\lib\idea_rt.jar
         */

    }
}

獲取執行時類的完整結構

通過反射獲取執行時類的完整結構

Field、Method、Constructor、Superclass、Interface、Annotation

  • 實現的全部介面
  • 所繼承的父類
  • 全部的構造器
  • 全部的方法
  • 全部的Field
  • 註解
  • ......
package com.lurenj.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

//獲得類的資訊
public class Test08 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class c1 = Class.forName("com.lurenj.reflection.User");

        //獲得類的名字
        System.out.println(c1.getName());//獲得包名 + 類名
        System.out.println(c1.getSimpleName());//獲得類名

        //獲得類的屬性
        System.out.println("================================");
        Field[] fields = c1.getFields();//只能找到public屬性
        fields = c1.getDeclaredFields();//可以找到全部屬性

        //快捷方式fields.for
        for (Field field:fields){
            System.out.println(field);
        }

        //獲得指定屬性的值
        Field name = c1.getDeclaredField("name");
        System.out.println(name);

        //獲得類的方法
        System.out.println("=========================");
        Method[] methods = c1.getMethods();//獲得本類及其父類的全部public方法
        for (Method method : methods) {
            System.out.println("正常的:" + method);
        }

        methods = c1.getDeclaredMethods();//獲得本類的所有方法
        for (Method method : methods) {
            System.out.println("getDeclaredMethods:" + method);
        }

        System.out.println("===============================");
        //獲得指定方法
        Method getName = c1.getMethod("getName", null);
        Method setName = c1.getMethod("setName", String.class);
        System.out.println(getName);
        System.out.println(setName);

        //獲得構造器
        System.out.println("=============================");
        Constructor[] constructors = c1.getConstructors();//獲得本類及其父類的全部public方法
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }

        constructors = c1.getDeclaredConstructors();//獲得本類的所有方法
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }

        //獲得指定的構造器
        Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
        System.out.println("指定的構造器:" + declaredConstructor);

    }
}

注意:

  1. 在實際的操作中,取得類的資訊的操作,並不會經常開發
  2. 一定要熟悉java.lang.reflect包的作用,反射機制
  3. 如何獲取屬性、方法、構造器的名稱,修飾符等