Jvm之class文件的加載、初始化

編寫的java文件在要真正運行時,會首先被編譯成 “.class"結尾的二進制文件,然後被虛擬機加載。那麼在虛擬機中一個class文件要成爲java實例,需要經歷好幾個步驟:
一、class文件要成爲java實例的步驟
1、裝載:裝載階段由三個基本動作完成,要裝載一個類型,java虛擬機必須:
(1)通過該類型的完全限定名,產生一個代表該類型的二進制數據流
(2)解析這個二進制數據流爲方法區內的內部數據結構
(3)創建一個表示該類型的java.lang.Class的實例
*當時用new關鍵字創建一個對象時,該類的數據結構存放在方法區中,new出的對象存放在堆中
二進制的數據流可由以下方式產生:
 1、從本地加載一個java class 文件
 2、通過網絡上下載一個class文件
3、從一個zip、jar、或者其他文檔中提取class文件    等…
2、驗證:類被裝載後,就要準備連接,連接的第一步是驗證——確認類型符合java語言規範,並且不會危及虛擬機的完整性。
類型的檢查——確保除了Object之外的每個類都必須有一個超類,並確保該類的所有超類都已經被裝載了
類之間的二進制兼容檢查——檢查final類不能用於子類、檢查final類的方法不能被覆蓋、確保子類和超類之間沒有不兼容的方法
——檢查所有的常量池入口相互之間一致
——檢查常量池中的所有特殊字符串是否符合格式
——檢查字節碼的完整性(最爲複雜的一步)
3、準備:在準備階段java虛擬機爲類變量分配內存,設置默認初始值,但在到達初始化之前,類變量都沒有被初始化爲真正的初始值。即:我們在類中聲明  int  a = 3;但在這一步,a的致被賦予類型的默認值 0  int a =0;java虛擬機不支持boolean 類型,在內部,boolean變量會被默認的設置爲int類型的0,即初始化成false.
4、解析:經過驗證和準備之後,就進入瞭解析過程。解析就是在類型的常量池中尋找類、接口、字段、以及方法的符號引用,把這些引用替換成爲直接引用的過程
5、初始化:初始化就是賦予一個變量真正的初始值
如我們定義 private static int a=1; 此時就是給a賦值1
二、動態鏈接和解析
  class文件把所有的符號引用保存在——常量池中,每一個 class文件都有一個常量池。每一個被虛擬機裝載類或者接口 都有一個內部版本的常量池,被稱爲運行時常量池。
常量池的解析——當程序運行時,某個特定的符號引用要被使用, 首先要被解析。解析過程就是根據 符號引用查找到實體, 在把符號引用替換成爲 直接引用的過程。每個符號引用都只被解析一次
早解析——預先解析所有的符號引用,從初始類開始,到後續的各個類,知道所有的符號引用都被解析。
遲解析——在訪問每一個符號引用的最後一刻纔去解析。(也可選擇兩種情況之間的折衷策略)
——程序執行都是在第一次 實際訪問一個符號引用時纔會拋出錯誤,對於用戶來說,看上去都是 遲解析
——java虛擬機會把所有具有相同字符串順序的字符串文字處理成一個String對象。即:如果有多個類使用同一個字符串“Hello”,java虛擬機只會創建一個具有“Hello ”值的String對象 來表示所有的字符串文字。
——任何的byte、short、char返傭www.fx61.com,的值在被壓入棧中時, 都會先被轉換成int型
——涉及byte、short、char的運算操作會首先把他們都轉換成int類型,進行計算然後得到int結果,如果需要byte等結果,需要進行顯示轉換。
——java虛擬機中內存只能以對象形式在堆中進行分配,如果需要可考慮基本類型包裝器
——java所使用的同步機制是監視器,java中的監視器支持兩種線程:互斥和協作。java虛擬機是通過鎖來實現互斥,是通過Object的wait和notify方法來實現協作
——只有當絕對確定等待區中只有一個線程掛起的時候才應使用notify,只要存在同時有多個線程掛起的可能性,就應該使用notify all。否則可能導致某個特定的線程在等待區中等待時間過長,甚至永遠就不會甦醒。
——堆和方法區是被所有線程共享的 
——會被多線程訪問的兩種數據:保存在堆中的實例變量,保存在方法區中的類變量
——不需要進行保護的變量:java棧中的局部變量,該數據是擁有該線程的線程私有的
——當虛擬機裝載一個class文件的時候,會創建一個java.lang.Class類的實例。當鎖住一個類的時候,其實就是鎖住那個類的Class對象。
三、jvm內置的三大類加載器
BootStrap類加載器(BootStrapClassLoader):根類加載器。該加載器沒有父加載器,負責加載虛擬機的核心類庫,如:java.lang.*等,java.lang.Object就是由根加載器加載的
Extension 類加載器(ExtClassLoader):它的父加載器爲根加載器,也就是上面那個。它從java.ext.dirs系統屬性所指定的目錄中加載類庫,或者從jdk的安裝目錄jre/lib/ext子目錄中加載類庫。該類是純java類是lava.lang.ClassLoader類的子類
System系統類加載器(AppClassLoader),也稱爲應用類加載器,其父加載器爲擴展類加載器,上面那個。它從環境變量classpath中多指定的目錄中加載。它是用戶自定義類加載器的默認父加載器,該類是純java類是lava.lang.ClassLoader類的子類
注意:使用Class.forName("com.test.Test1")進行類裝載時,會自動執行類中的靜態代碼塊,但不會執行構造方法
使用loadClass("com.test.Test1")進行裝載時,不會自動執行其中的靜態代碼塊,也不會執行構造方法
***:同一個類,由兩個不同的類加載器去加載,會被認爲是兩個不相同的類,在方法區中會有兩份該類的類信息
只有使用啓動類加載器加載的類,比如:java.lang.Strong、java.lang.Object 等類,在方法區中只有一份類信息。
 判斷兩個類是否相等的基礎是,這兩個類的類加載器是不是同一個
初始化:對於類的初始化階段,虛擬機規範規定了5種情況下必須立即對類進行初始化
1、遇到new、getstatic、putstatic、invokestatic這四條字節碼指令時,如果類之前沒有進行過初始化會進行初始化。這四條字節碼對應的是:new 實例、靜態字段取值、靜態字段賦值、靜態字段調用。
2、使用java.lang.refleat包的方法進行反射調用
3、當初始化一個類時,如果發現其父類還沒有被初始化,需要先初始化其父類
4、當虛擬機啓動,需要執行main方法的類
5、當使用LDK1.7的動態預言支持時,如果一個java.lang.invole.MethodHandle實例的最後解析結果是REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄。
以下幾種情況需要注意:
1、對於靜態字段,只有直接定義這個字段的類或者接口才會被初始化,如果是通過子類引用父類中的靜態字段,只會觸發父類的初始化。
2、常量在編譯階段會存入調用類的常量池中,本質上並沒有直接引用到定義類的常量,因此不會觸發定義常量的類的初始化。備註:只有編譯器能確定之的常量纔會進行該操作,編譯器無法確定的常量值,還是會觸發目標類的初始化。
例如:public static final String uuid = UUID.randomUUID().toString();  這行代碼,當其他類在引用uuid時,會觸發定義該uuid的類的初始化
3、通過數組定義來引用類,不會觸發類的初始化 例如: Super[ ] s = new Super[10]; 這行代碼並不會觸發Super類的初始化
4、當一個接口初始化的時候,並不要求其父接口也被初始化,只有真正使用到父接口的時候纔會初始化。
四、case class與class的區別
1、初始化的時候,不需要new,當然你也可以加上,普通類一定需要加上new
class ABC(name:String){
def ff(): Unit ={
}
}
case class ABC1(name:String){
def ff1(): Unit ={
}
}
val abc1=ABC1("fg")
val abc= new ABC("xx")
2、toString的實現更加漂亮
println(abc1.toString)
println(abc.toString)
3、默認實現了equals   hashcode
4、默認是可以序列化的,也就是實現了Serializable
5、自動從scala.Producet中繼承了一些函數
6、case class構造函數的參數是publiec級別的,我們可以直接訪問
7、支持模式匹配
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章