jvm學習筆記之類的加載、連接與初始化——2019-04-04


title: jvm學習筆記之類的加載、連接與初始化——2019-04-04
date: 2019-04-04
tags: java

jvm學習筆記之類的加載、連接與初始化——2019-04-04

類的加載、連接與初始化

  1. 加載:查找並加載類的二進制數據
  2. 連接
    1. 驗證:確保被加載類的正確性
    2. 準備:爲類的靜態變量分配內存,並將其初始化爲默認值
    3. 解析:把類中的符號引用轉換爲直接引用
  3. 初始化:爲類的靜態變量賦予正確的初始值

如:
Class test{
Public static int a=1;
}
在test類在被加載時,靜態變量a首先被分配內存,設置默認值a=0
接着在初始化過程中才被賦予正確的初始值a=1

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

主動使用
被動使用

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

主動使用(七種)
 創建類的實例(new一個類對象)
 訪問某個類或接口的靜態變量,或者對該靜態變量賦值(對靜態變量取值賦值)
助記符 getstatic putstatic
 調用類的靜態方法 助記符 invokestatic
 反射(如Class.forName(“com.test.Test”))
 初始化一個類的子類
如:
Class Parent{}
Class Child extends Parent{}
當子類被初始化時,同時也標記着父類的主動使用,父類也會被初始化
 Java虛擬機啓動時被標明爲啓動類的類(Java Test)
 JDK1.7開始提供的動態語言支持
Java.lang.invoke.MethodHandle實例的解析結果REF_getStatic, REF_putStatic, REF_invokeStatic句柄對應的類沒有初始化,則初始化

除了以上七種情況,其它使用java類的方式都被看作是對類的被動使用,都不會導致類的初始化

類的加載
類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然後在內存中創建一個java.lang.Class對象(規範並未說明Class對象位於哪裏,大致上放在堆區,HotSpot虛擬機將其放在了方法區中)用來封裝類在方法區內的數據結構

 加載.class文件的方式

 從本地系統中直接加載(ide工作區重啓加載項目即是如此)
 通過網絡下載.class文件
 從zip,jar等歸檔文件中加載.class文件
 從專有數據庫中提取.class文件
 將Java源文件動態編譯爲.class文件(如JSP中java代碼的編寫,實際上是轉化成了Servlet)

(主動使用)
測試代碼一

package com.lagoon.jvm.classloder;

public class MyTest1 {

    public static void main(String[] args) {
        System.out.println(MyChild1.str);
    }
}

class MyParent1{

    public static String str="hello world";

    static {
        System.out.println("MyParent1 static block");
    }
}

class MyChild1 extends MyParent1{
    static {
        System.out.println("MyChild1 static block");
    }
}

測試結果
在這裏插入圖片描述

並沒有輸出MyChild1 static block
這種情況稱之爲對MyParent1的一個主動使用,但是並沒有對MyChild1進行主動使用
所以並不會對MyChild1進行初始化,也就不會執行靜態代碼塊

測試代碼二

package com.lagoon.jvm.classloder;

public class MyTest1 {

    public static void main(String[] args) {
        System.out.println(MyChild1.str2);
    }
}

class MyParent1{

    public static String str="hello world";

    static {
        System.out.println("MyParent1 static block");
    }
}

class MyChild1 extends MyParent1{
    public static String str2="welcome...";
    static {
        System.out.println("MyChild1 static block");
    }
}

測試結果
在這裏插入圖片描述

此時在MyChild1中定義了一個str2,並在main方法中調用的str2,是對MyChild類的一次主動使用,自然會初始化,自然也就會執行靜態代碼塊,輸出語句
而對於爲什麼MyParent1也會輸出語句?
是因爲主動使用裏有一條
出初始化一個類的子類,那麼也就是這個父類也會被主動使用,進行一次初始化。
父類會進行先行初始化

測試總結:
對於一個靜態字段來說,只有直接定義了該字段的類纔會被初始化
當一個類在初始化時,要求其父類全部都已經初始化完畢
這就是問什麼輸出語句有先後順序

有關jvm虛擬機vm設置指令

-XX:+TraceClassLoading,用於追蹤類的加載信息並打印出來
 -XX:+<option>,表示開啓option選項
 -XX:-<option>,表示關閉option選項
*-XX:<option>=<value>,表示將option的值設置爲value

再新建一個測試類MyTest2

package com.lagoon.jvm.classloder;

public class MyTest2 {

    public static void main(String[] args) {
        System.out.println(MyParent2.str);
    }
}
class MyParent2{
    public static String str="hello,world";

    static {
        System.out.println("MyParent2 static block");
    }
}

測試結果
在這裏插入圖片描述

顯而易見是在MyParent2裏定義了靜態字段,
所以會對這個類進行初始化,運行靜態代碼塊
但是如果把

public static  String str="hello,world";

改成

public static final String str="hello,world";

加上關鍵字final
運行結果如下
在這裏插入圖片描述

產生這種差異的原因是

final本身的作用在於str被定義以後不能再被改變,str成爲常量,
然而常量在編譯階段,會被直接存入到調用這個常量的方法所在類(MyTest2)的常量池中
之後,MyTest2與MyParent2就沒有任何的關係了

因此本質上,調用類,也就是MyTest2,並沒有直接引用到定義這個常量的類,也就是
MyParent2,因此不會觸發定義常量的類的初始化

對MyTest2進行反編譯後如下

在這裏插入圖片描述

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

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