1. 程式人生 > 實用技巧 >Java高階特性——反射機制(第二篇)

Java高階特性——反射機制(第二篇)

在Java高階特性——反射機制(第一篇)中,寫了很多反射的例項,可能對於Class的瞭解還是有點迷糊,那麼我們試著從記憶體角度去分析一下。

Java記憶體

從上圖可以看出,Java將記憶體分為堆、棧、方法區其中方法區是一種特殊的堆

:堆中通常存放new的物件和陣列,可以被所有的執行緒共享,不會存放別的物件引用。

:存放基本的變數型別(會包含這個基本型別的具體數值)以及引用物件的變數(會存放這個引用在堆裡邊的具體地址)。

方法區:可以被所有的執行緒共享,包含了所有的class和static變數。‘

類的載入過程(瞭解即可)

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

第一步(載入):將類的 .class 檔案讀入記憶體,並將這些靜態資料轉化為方法區執行時的資料結構,然後生成一個代表這個類的java.lang.Class物件。這個過程由類載入器完成。

第二步(連結):將Java類的二進程式碼合併到JVM的執行環境JRE中,分為下面三個步驟:

        >驗證:確保載入類的資訊符合JVM規範,並沒有安全方面的問題。

        >準備:正式為類變數(static)分配記憶體並設定類變數預設初始值的階段,這些記憶體都將在方法區中進行分配。

        >解析:虛擬機器常量池內的符號引用(常量名)替換為直接引用(地址)的過程。

第三部(初始化):JVM負責對類進行初始化,過程如下:

        >執行類構造器<clinit>()方法的過程,類構造器<clinit>()方法是由編譯期自動收集類所有類變數的賦值動作和靜態程式碼塊中的語句合併產生的。(類構造器是構造類資訊的,不是構造該類物件的構造器)

        >當初始化一個類的時候,如果發現其父類還沒有初始化,則需要先觸發其父類先初始化。

·        >虛擬機器會保證一個類<clinit>()方法在多執行緒環境中正確加鎖和同步。

舉例測試:

package test;

import
java.lang.annotation.ElementType; public class Test{ public static void main(String[] args) { A a = new A(); System.out.println(a.m); /* * 1.載入到記憶體,會產生一個類對應的class物件 * 2.連結:連結結束後,m=0 * 3.初始化 * <clinit>(){ * System.out.println("A類的靜態程式碼塊被載入"); m=300; m=100; * } * 最後:m=100 */ } } class A{ static { System.out.println("A類的靜態程式碼塊被載入"); m=300; } static int m=100; public A() { System.out.println("A類的無參構造方法初始化"); } }

執行結果:

類的初始化

什麼時候會發生類的初始化?

1.類的主動引用(一定會發生類的初始化)

>當虛擬機器啟動時,先初始化main方法所在的類。

>new一個類的物件。

>呼叫類的靜態成員(除了final常量)和靜態方法。

>使用java.lang.reflect包的方法對類的反射進行呼叫。

>當初始化一個類,當父類沒有被初始化,則會先初始化它的父類。

舉例(2-1):

package test;


//測試類什麼時候會被初始化
public class Test{
    static {
        System.out.println("main類被載入");
    }
    
    public static void main(String[] args) throws ClassNotFoundException {
        
        //主動引用
        //Son son = new Son();
        
        
        //反射也會產生主動引用
        Class.forName("test.Son");
        
        
        //兩者輸出結果均一樣
        /*
         *   結果為:
         *  main類被載入
         *    父類被載入
         *   子類被載入
         */
    }
    
}

class Father{
    
    static int b = 1;
    
    static {
        System.out.println("父類被載入");
    }
}

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

列印結果為:

2.類的被動引用(不會發生類的初始化)

>當訪問一個靜態域時,只有真正宣告這個域的類才會被初始化。例如:當通過子類引用父類的靜態變數,不會導致子類初始化。

>通過陣列定義類引用,不會觸發此類的初始化。

>引用常量不會觸發此類的初始化(常量在連結階段就存入呼叫類的常量池中了)。

舉例(改變例2-1的main方法):

public static void main(String[] args) throws ClassNotFoundException {
        //不會產生引用的方法
        //System.out.println(Son.b);
        
        //Son[] sons = new Son[10];
        
        System.out.println(Son.M);
    }

執行結果一個一個去測試一下!

類載入器

>類載入器的作用:將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉化為方法區執行時資料結構,然後在堆中生成一個java.lang.Class物件,作為方法區中類資料的訪問入口。

>類快取:標準的Java SE類可以按要求查詢類,但是一旦某個類被載入到類載入器中,它將維持載入(快取)一段時間,不過JVM垃圾回收機制(gc)可以回收這些Class物件。

類載入器的作用

類載入器的作用:把類(class)裝載進記憶體。

JVM規範定義瞭如下型別的類載入器:

>引導載入器(Bootstap ClassLoader):用C++編寫的,是JVM自帶的類載入器,負責Java平臺的核心庫,用來裝載核心類庫(rt.jar),該載入器無法直接獲取。

>擴充套件類載入器(Extension ClassLoader):負責jre\lib\ext目錄下的jar包或 -D java.ext.dirs指定目錄的jar包裝入工作室。

>系統載入器(System ClassLoader):負責java -classpath 或者-D java.class.path所指目錄下的類與jar包裝入工作,是最常用的載入器。

舉例獲取載入器:

package test;



public class Test{
    
    public static void main(String[] args) throws ClassNotFoundException {
        //獲取類載入器
        ClassLoader c1 = ClassLoader.getSystemClassLoader();
        System.out.println(c1);
        
        //獲取類載入器的父類載入器-->拓展類載入器
        ClassLoader c2 = c1.getParent();
        System.out.println(c2);
        
        //獲取拓展類載入器的父類載入器-->根載入器(C/C++ Tip:獲取不到返回NULL)
        ClassLoader c3 = c2.getParent();
        System.out.println(c3);
        
        //測試當前載入器是由哪個類載入的
        ClassLoader c4 = Class.forName("test.Test").getClassLoader();    
        System.out.println(c4);
        
        //測試JDK是由哪個載入器載入的
        ClassLoader c5 = Class.forName("java.lang.Object").getClassLoader();        
        System.out.println(c5);
        
        //如何獲取系統類載入器可以載入的路徑
        System.out.println(System.getProperty("java.class.path"));
        
        //雙親委派機制  學習連結(https://blog.csdn.net/shy415502155/article/details/88167713
        
        
        /*
         C:\Program Files\Java\jdk1.8.0_131\jre\lib\resources.jar;
         C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar;
         C:\Program Files\Java\jdk1.8.0_131\jre\lib\jsse.jar;
         C:\Program Files\Java\jdk1.8.0_131\jre\lib\jce.jar;
         C:\Program Files\Java\jdk1.8.0_131\jre\lib\charsets.jar;
         C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfr.jar;
         C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\access-bridge-64.jar;
         C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar;
         C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\dnsns.jar;
         C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jaccess.jar;
         C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar;
         C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\localedata.jar;
         C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\nashorn.jar;
         C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunec.jar;
         C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar;
         C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar;
         C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar;
         C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\zipfs.jar;
         G:\test\bin
         */
    }
    
}

列印結果: