- 類加載
- 在java代碼中,類型的加載、連接配接、初始化過程都是在程式運作期間完成的
-
提供了更大的靈活性,增加了 更多的可能性
解釋:
△注意此處的 “類型” 指的是類,也就是具體的 class, 而非對象。 舉個例子就是:加載的 Objcet 這個類的二進制位元組碼檔案,而不是 new Object 的對象,在這個階段不存在對象的概念。
△它加載的來源有這麼幾種:①磁盤上的位元組碼②動态生成的位元組碼③網絡來源
△還有一個重要的概念是 runtime,運作期間完成的,好多編譯型語言都是在編譯時完成的加載和連接配接,而java這樣做,印證了第二句話。
延伸:加載、連接配接、初始化JVM并非是嚴格按照這個順序,JVM有相應的規範,但規範對某些地方做了詳細規定,對有些地方僅做了一些描述。這樣的話虛拟機廠商就可以自由的發揮了,隻要最終的要求實作功能一緻就可以,不用拘泥于其底層是怎麼實作的,是不是和JDBC的很像?
疑問:java本身是一門靜态類型的語言,很多的特性又具有動态語言才能擁有的特質?
- 類加載器
- 顧名思義,既然是“器”,那就是對應的類型加載的具體實作。而類加載做的事情不過是将位元組碼檔案,無論何處來的,加載到記憶體當中,供JVM機程序使用。
-
JVM與程式的生命周期(遇到以下幾種情況,java虛拟機将結束聲明周期):
①執行了System.exit();
②程式正常執行結束
③程式在執行過程中遇到了異常或錯誤而異常終止
④由于作業系統出現錯誤導緻JVM終止
- 類的加載、連接配接、初始化
- 加載:查找并加載類的二進制資料 (類加載器對應于這一過程)
-
連接配接
△ 驗證:確定被加載類的正确性
要了解的是java隻不過是一門語言,是友善我們程式員來進行程式設計,提高效率而呈現出的一種語言形式,其核心在JVM機和運作的位元組碼檔案,如果說其他的語言編譯後也符合class檔案的格式,那麼它也能在JVM機上運作,和java沒有差別。語言隻不過是程式員和class檔案之間的橋梁而已。你要是牛逼直接寫二進制位元組碼檔案也可以,但是不現實,效率太低,沒有必要。是以在被類型加載之後所面臨的問題是:驗證位元組碼檔案的正确性。因為很容易被篡改,無論是磁盤上的人為操作,或者網絡上的位元組碼檔案
△ 準備:為類的靜态變量配置設定記憶體,并将其初始化為預設值
可以看到首要做的就是為靜态變量指派。不管有沒有指派,都得先初始化為預設值,之後在初始化階段再指派。可見程式并不聰明
△ 解析:把類中的符号引用轉換為直接引用
畫個圖來解釋直接引用和間接引用:
- 初始化:為類的靜态變量賦予正确的初始值
- 使用:調用對象、相關方法。 (運作中)
- 解除安裝: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("子類初始化"); } }
- 問:在第一個運作程式中子類既然沒有初始化,那麼它加載了嗎?
- 首先加載的類是Object類,在C:\Program Files\Java\jdk1.8.0_121\jre\lib\rt.jar下
可以看到三個類依次的加載順序
-XX:- 表示開啟option選項
-XX:+表示關閉option選項
-XX:=表示将option選項的值設定為value
- 案例2:
常量在編譯階段會存到調用該常量的方法所在類的常量池中。本質上,調用類并沒有直接引用到定義常量的類,是以并不會觸發定義常量類的初始化。
注意:這裡指的是将常量存放到了MyTest2常量池中,之後MyTest2和MyParent2就沒有任何關系,甚至我們可以将MyParent2的class檔案删除
反編譯Test2.class: 解釋:這裡的助記符其實都有對應的類實作,快捷鍵CTRL+SHIFT+T,查找對應的助記符(大寫),會發現對應的類。如下圖: - 運作期常量 仿照上一個案例,删掉編譯後的Parent3.class檔案,報出下列錯誤: 當一個靜态常量的值并不能夠在編譯期确定下來,那麼其值就不會放到調用類的常量池中。這時候程式運作時,會導緻主動使用這個常量所在的類,顯然會導緻這個類被初始化
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在運作期間動态生成的,表示為[Lcom.test.Parent4,這種形式。動态生成的數組其父類是Object類。并不是com.test.Parent4的執行個體,是以不在主動使用的範疇之内。
對于數組來說,javadoc經常将構成數組的元素稱為Component,實際上就是将數組降低一個次元後的類型。比如二維數組對應的就是一維數組,一維數組就對應具體的類型。反編譯代碼如下: