天天看點

深入了解JVM(1)

  1. 類加載
  • 在java代碼中,類型的加載、連接配接、初始化過程都是在程式運作期間完成的
  • 提供了更大的靈活性,增加了 更多的可能性

    解釋:

    △注意此處的 “類型” 指的是類,也就是具體的 class, 而非對象。 舉個例子就是:加載的 Objcet 這個類的二進制位元組碼檔案,而不是 new Object 的對象,在這個階段不存在對象的概念。

    △它加載的來源有這麼幾種:①磁盤上的位元組碼②動态生成的位元組碼③網絡來源

    △還有一個重要的概念是 runtime,運作期間完成的,好多編譯型語言都是在編譯時完成的加載和連接配接,而java這樣做,印證了第二句話。

    延伸:加載、連接配接、初始化JVM并非是嚴格按照這個順序,JVM有相應的規範,但規範對某些地方做了詳細規定,對有些地方僅做了一些描述。這樣的話虛拟機廠商就可以自由的發揮了,隻要最終的要求實作功能一緻就可以,不用拘泥于其底層是怎麼實作的,是不是和JDBC的很像?

    疑問:java本身是一門靜态類型的語言,很多的特性又具有動态語言才能擁有的特質?

  1. 類加載器
  • 顧名思義,既然是“器”,那就是對應的類型加載的具體實作。而類加載做的事情不過是将位元組碼檔案,無論何處來的,加載到記憶體當中,供JVM機程序使用。
  • JVM與程式的生命周期(遇到以下幾種情況,java虛拟機将結束聲明周期):

    ①執行了System.exit();

    ②程式正常執行結束

    ③程式在執行過程中遇到了異常或錯誤而異常終止

    ④由于作業系統出現錯誤導緻JVM終止

  1. 類的加載、連接配接、初始化
    深入了解JVM(1)
  • 加載:查找并加載類的二進制資料 (類加載器對應于這一過程)
  • 連接配接

    △ 驗證:確定被加載類的正确性

    要了解的是java隻不過是一門語言,是友善我們程式員來進行程式設計,提高效率而呈現出的一種語言形式,其核心在JVM機和運作的位元組碼檔案,如果說其他的語言編譯後也符合class檔案的格式,那麼它也能在JVM機上運作,和java沒有差別。語言隻不過是程式員和class檔案之間的橋梁而已。你要是牛逼直接寫二進制位元組碼檔案也可以,但是不現實,效率太低,沒有必要。是以在被類型加載之後所面臨的問題是:驗證位元組碼檔案的正确性。因為很容易被篡改,無論是磁盤上的人為操作,或者網絡上的位元組碼檔案

    △ 準備:為類的靜态變量配置設定記憶體,并将其初始化為預設值

    可以看到首要做的就是為靜态變量指派。不管有沒有指派,都得先初始化為預設值,之後在初始化階段再指派。可見程式并不聰明

    △ 解析:把類中的符号引用轉換為直接引用

    畫個圖來解釋直接引用和間接引用:

    深入了解JVM(1)
  • 初始化:為類的靜态變量賦予正确的初始值
  • 使用:調用對象、相關方法。 (運作中)
  • 解除安裝:class檔案駐留在記憶體中使用完之後當然要解除安裝,一方面釋放記憶體,另一方面友善後來人
  • java程式對類的使用方式可以分為兩種

    △ 主動使用

    ①建立類執行個體

    ②通路某個類或者接口的靜态變量(getstatic),或者對該靜态變量指派(putstatic),調用該類的靜态方法(invokestatic)。

    ③反射(如Class.forName(“com.test.Test”))

    ④初始化一個類的子類

    很有意思啊這裡,隻有主動使用才會初始化,而方式隻有這六種,而一個類的子類初始化那麼對應的隻有5種,加上最後一種不常見,是以有四種。看下面代碼

    ⑤JVM啟動時被标明為啟動類的類(含有main方法的類)

    ⑥JDK1.7開始提供的動态語言支援java.lang.invoke.MethodHandle執行個體的解析結果REF_getStatic,REF_putStatic,REF_invokeStatic對應類沒有初始化

    △ 被動使用

    除了主動使用之外就是被動使用,他兩構成了一個全集

  • 所有的JVM實作都遵循了:在每個類或接口被JAVA程式“ 首次主動使用 ”時才初始化它們。

    除了以上六種情況之外,其他的都屬于被動使用,都不會導緻類的初始化。記住是初始化!加載、連接配接階段可能有,也可能沒有,唯一能夠确定的是肯定沒有初始化

  • 類的加載指的是将類的.class檔案中的二進制資料讀入到記憶體中,将其放在運作時資料區的方法區内,然後記憶體中建立了一個java.lang.Class對象,用來封裝在方法區内的資料結構。再次明确位元組碼檔案加載的方式:

    ①從本地系統磁盤上加載

    ②通過網絡下載下傳

    ③從zip、jar等歸檔檔案中加載.class檔案

    ④從專有資料庫中提取位元組碼檔案

    ⑤将java源檔案動态編譯為位元組碼檔案(動态代理、web開發)

  • 案例:
    package com.test;
    
    public class Test1 {
    	static {
    		System.out.println("main方法初始化");
    	}
    	public static void main(String[] args) {
    		System.out.println(Child.p);
    	}
    }
    
    class Parent{
    	public static String p = "我是爸爸";
    	
    	static {
    		System.out.println("父類初始化");
    	}
    }
    
    class Child extends Parent{
    //	public static String p = "我是兒子";
    	static {
    		System.out.println("子類初始化");
    	}
    }
               
    深入了解JVM(1)
    去除注釋之後:
    深入了解JVM(1)
    注:父類必須先行初始化
    • 問:在第一個運作程式中子類既然沒有初始化,那麼它加載了嗎?
    • 深入了解JVM(1)
      深入了解JVM(1)
      首先加載的類是Object類,在C:\Program Files\Java\jdk1.8.0_121\jre\lib\rt.jar下
      深入了解JVM(1)

      可以看到三個類依次的加載順序

      -XX:- 表示開啟option選項

      -XX:+表示關閉option選項

      -XX:=表示将option選項的值設定為value

  • 案例2:
    深入了解JVM(1)
    深入了解JVM(1)

    常量在編譯階段會存到調用該常量的方法所在類的常量池中。本質上,調用類并沒有直接引用到定義常量的類,是以并不會觸發定義常量類的初始化。

    注意:這裡指的是将常量存放到了MyTest2常量池中,之後MyTest2和MyParent2就沒有任何關系,甚至我們可以将MyParent2的class檔案删除

    深入了解JVM(1)
    反編譯Test2.class:
    深入了解JVM(1)
    深入了解JVM(1)
    深入了解JVM(1)
    解釋:這裡的助記符其實都有對應的類實作,快捷鍵CTRL+SHIFT+T,查找對應的助記符(大寫),會發現對應的類。如下圖:
    深入了解JVM(1)
    深入了解JVM(1)
    深入了解JVM(1)
  • 運作期常量
    深入了解JVM(1)
    仿照上一個案例,删掉編譯後的Parent3.class檔案,報出下列錯誤:
    深入了解JVM(1)
    當一個靜态常量的值并不能夠在編譯期确定下來,那麼其值就不會放到調用類的常量池中。這時候程式運作時,會導緻主動使用這個常量所在的類,顯然會導緻這個類被初始化
    深入了解JVM(1)
    package com.test;
    public class Test4 {
    	public static void main(String[] args) {
    		//首次主動使用
    //		Parent4 parent4 = new Parent4();
    		Parent4 parent4[] = new Parent4[1];
    		System.out.println(parent4.getClass());
    		System.out.println(parent4.getClass().getSuperclass());
    		Parent4 parent5[][] = new Parent4[1][1];
    		System.out.println(parent5.getClass());
    		System.out.println(parent5.getClass().getSuperclass());
    	}
    }
    class Parent4{
    	static {
    		System.out.println("Parent4");
    	}
    }
               
    深入了解JVM(1)

    對于數組執行個體來說,其類型是由JVM在運作期間動态生成的,表示為[Lcom.test.Parent4,這種形式。動态生成的數組其父類是Object類。并不是com.test.Parent4的執行個體,是以不在主動使用的範疇之内。

    對于數組來說,javadoc經常将構成數組的元素稱為Component,實際上就是将數組降低一個次元後的類型。比如二維數組對應的就是一維數組,一維數組就對應具體的類型。反編譯代碼如下:

    深入了解JVM(1)
    深入了解JVM(1)