java-6: java類從加載、連接到初始化過程詳解 - 引出new()和newinstance()區別

鏈接:https://blog.csdn.net/xulianzhen/article/details/79394223

博主:xulianzhen

問題來源:new()和反射的newinstance()有什麼區別

---------------------------------------第一部分:先看java類創建之前的過程如下--------------------------------

Java代碼在編譯後會轉化成Java字節碼,字節碼被類加載器加載到JVM裏,JVM執行字節碼,最終需要轉化成彙編指令在CPU上執行,Java中所使用的併發機制依賴於JVM的實現和CPU的指令。

類加載期的類加載過程:

在Java代碼中,類型的加載、連接與初始化過程都是在程序運行期間完成的。

 

在如下幾種情況下,Java虛擬機將結束生命週期:

1. 執行了System.exit()方法

2. 程序正常執行結束

3. 程序在執行過程中遇到了異常或錯誤而異常終止

4. 由於操作系統出現了錯誤而導致Java虛擬機進程終止

 

類的字節碼從磁盤上進入到內存裏的五個大的階段:

1. 加載:查找並加載類的二進制數據

2. 連接:

- 驗證: 確保被加載的類的正確性

- 準備:爲類的靜態變量分配內存,並將初始化默認值(類的實例變量還沒有)

- 解析:把類中的符號引用轉換爲直接引用

3. 初始化:爲類的靜態變量賦予正確的初始值

4. 使用(如創建類的對象,調用類的方法)

5. 卸載(將駐留在內存裏的類的數據結構銷燬掉,卸載後不能再使用該類創建對象了)

類的加載:

  • 類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然後在內存中創建一個java.lang.Class對象(HotSpot虛擬機將Class對象放在了方法區中)用來封裝類在方法區的數據結構。
  • 加載.class文件的方式

- 從本地系統中直接加載

- 通過網絡下載.class文件

- 從zip,jar等歸檔文件中加載.class文件

- 從專用數據庫中提取.class文件

- 將Java源文件動態編譯爲.class文件(如web開發中的jsp會在運行的時候轉換爲servlet的.class文件)

 

初始化時機:

  • Java程序對類的使用方式可分爲兩種:

    - 主動使用

    - 被動使用

  • 所有的Java虛擬機實現必須在每個類或接口被Java程序“首次主動使用”時才初始化他們。
  • 主動使用(七種)

- 創建類的實例

- 訪問某個類或接口的靜態變量(助記符:getstatic)(但static final的常量除外,它在常量池中),或者對該靜態變量賦值(助記符:putstatic)

- 調用類的靜態方法(助記符:invokestatic)

- 反射(如Class.forName("com.test.Test"))

- 初始化一個類的子類

- Java虛擬機啓動時被標明爲啓動類的類(Java Test,即含有main()方法的類)

- JDK1.7開始提供的動態語言支持:

java.lang.invoke.MethodHandle實例的解析結果REF_getStatic,REF_putStatic,REF_invokeStatic句柄對應的類沒有初始化,則初始化

 

  • 除了以上七種情況,其他使用Java類的方式都被看作是對類的被動使用,都不會導致類的初始化。不被初始化的類不影響它可不可以加載和連接等步驟的執行,可以通過-XX:+TraceClassLoading虛擬機參數用於追蹤類的加載信息並打印出來。
  • 助記符:java字節碼中用一些編碼來表示指令的一些動作含義,使用助記符更好的幫助我們去記憶和理解這些字節碼指令。編碼、助記符和指令之間的對應關係可查閱:虛擬機字節碼指令表

 
  1. /**

  2. * 主動使用-初始化

  3. *

  4. ** 對於靜態字段來說,只有直接定義了該字段的類纔會被初始化

  5. ** 當一個類在初始化時,要求其父類全部都已經初始化完畢了

  6. */

  7. public class MyTest1 {

  8. public static void main(String[] args) {

  9. System.out.println(MyChild1.parentStr); // 執行1

  10. // System.out.println(MyChild1.childStr); // 執行2

  11. }

  12. }

  13.  
  14. class MyParent1 {

  15. public static String parentStr = "parentStr";

  16.  
  17. static {

  18. System.out.println("MyParent1 static");

  19. }

  20. }

  21.  
  22. class MyChild1 extends MyParent1 {

  23. public static String childStr = "childStr";

  24.  
  25. static {

  26. System.out.println("MyChild1 static");

  27. }

  28. }

執行一:

 

執行二:

 

加入-XX:+TraceClassLoading虛擬機參數執行一:

說明雖然虛擬機沒有對MyChild1進行初始化,但JVM也完成了對MyChild1的加載。

 

虛擬機參數三種格式:

-XX:+<option> 表示開啓option選項

-XX:-<option> 表示關閉option選項

-XX:<option>=<value> 表示將option選項的值設置爲value

 


 
  1. /**

  2. * 主動使用-初始化

  3. * 常量在編譯階段就會被存入到調用這個常量的方法所在的類的常量池中,

  4. * 本質上,調用類並沒有直接引用到定義常量的類,因此並不會觸發定義常量的類的初始化

  5. * 注意:這裏指的是將常量存放到了MyTest2的常量池中,之後MyTest2與MyParent2就沒有任何關係了

  6. * 甚至,我們可以將MyParent2的class文件刪除

  7. *

  8. * 助記符:ldc表示將int,float或是String類型的常量值從常量池中推送至棧頂。

  9. * bipush表示將單字節(-128~127)的常量值推送至棧頂

  10. * sipush表示將一個短整型常量值(-32768-32767)推送至棧頂

  11. * iconst_1表示將int類型的1推送至棧頂(iconst_1 ~ iconst_5)

  12. */

  13. public class MyTest2 {

  14. public static void main(String[] args) {

  15. System.out.println(MyParent2.parentStr);

  16. System.out.println(MyParent2.s);

  17. System.out.println(MyParent2.i);

  18. System.out.println(MyParent2.m);

  19. }

  20. }

  21.  
  22. class MyParent2 {

  23. // public static String parentStr = "parentStr"; // 執行1

  24. public static final String parentStr = "parentStr final"; // 執行2

  25.  
  26. public static final short s = 127;

  27.  
  28. public static final int i =128;

  29.  
  30. public static final int m = 2;

  31.  
  32. static {

  33. System.out.println("myParent2 static");

  34. }

  35. }

執行一:

 

執行二:

 

反編譯MyTest2.class:

 

總結:

圖片來自於網絡,若侵權刪

------------------------------ 第二部分:new()和反射的newinstance()有什麼區別--------------------------

以上文章爲鋪墊,回到問題引出:new()和反射的newinstance()有什麼區別

轉載博文:https://blog.csdn.net/qq_33704186/article/details/86596614

博主:stepMoreForever

-------------------------------

new和newInstance的區別

1.類的加載方式不同

在執行Class.forName("xxyy.class.Name")時,JVM會在classapth中去找對應的類並加載,這時JVM會執行該類的靜態代碼段。   
在使用newInstance()方法的時候,必須保證這個類已經加載並且已經連接了,而這可以通過Class的靜態方法forName()來完成的。  
使用關鍵字new創建一個類的時候,這個類可以沒有被加載,一般也不需要該類在classpath中設定,但可能需要通過classlaoder來加載。

2.所調用的構造方法不盡相同

new關鍵字能調用任何構造方法。
newInstance()只能調用無參構造方法。

3.執行效率不同

new關鍵字是強類型的,效率相對較高。
newInstance()是弱類型的,效率相對較低。

4.其他(初始化時機

既然使用newInstance()構造對象的地方通過new關鍵字也可以創建對象.
forname()會導致類被初始化,newInstance()纔會實例化,而new()操作等於初始化+實例化。

5.適用(應用的靈活性

使用newInstance()在通用性方面比較高,className我們可以用配置文件進行相關的配置。
String className = 從配置文件中讀取className; 
A a = (A) Class.forName(className).newInstance();
再配合依賴注入的方法,就提高了軟件的可伸縮性、可擴展性。框架的開發中用的比較多!
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        String str = (String) Class.forName("java.lang.String").newInstance();
        String str1 = new String();
        if(str.getClass() == str1.getClass()){
            System.out.println("YES");
        }
    }
    
output:YES

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章