一、目錄
二、類加載機制流程
1、什么是類加載機制?
JVM把class文件加載到內存里面,並對數據進行校驗、准備、解析和初始化,最終能夠被形成被JVM可以直接使用的Java類型的過程。
2、類加載流程圖
3、加載
- 將class文件加載在內存中。
- 將靜態數據結構(數據存在於class文件的結構)轉化成方法區中運行時的數據結構(數據存在於JVM時的數據結構)。
- 在堆中生成一個代表這個類的java.lang.Class對象,作為數據訪問的入口。
4、鏈接
鏈接就是將Java類的二進制代碼合並到java的運行狀態中的過程。
- 驗證:確保加載的類符合JVM規范與安全。
- 准備:為static變量在方法區中分配空間,設置變量的初始值。例如static int a=3,在此階段會a被初始化為0,其他數據類型參考成員變量聲明。
- 解析:虛擬機將常量池的符號引用轉變成直接引用。例如"aaa"為常量池的一個值,直接把"aaa"替換成存在於內存中的地址。
- 符號引用:符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。符號引用與虛擬機實現的內存布局無關,引用 的目標並不一定已經加載到內存中。
- 直接引用:直接引用可以是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。直接引用是與虛擬機實現的內存布局相關的,如果有了直接引用,那么引用的目標必定已經在內存中存在。
5、初始化
初始化階段是執行類構造器<clinit>()方法。在類構造器方法中,它將由編譯器自動收集類中的所有類變量的賦值動作(准備階段的a正是被賦值a)和靜態變量與靜態語句塊static{}合並,初始化時機后續再聊。
6、使用
正常使用。
7、卸載
GC把無用對象從內存中卸載。
三、類加載與初始化時機
1、類加載時機
當應用程序啟動的時候,所有的類會被一次性加載嗎?估計你早已知道答案,當然不能,因為如果一次性加載,內存資源有限,可能會影響應用程序的正常運行。那類什么時候被加載呢?例如,A a=new A(),一個類真正被加載的時機是在創建對象的時候,才會去執行以上過程,加載類。當我們測試的時候,最先加載擁有main方法的主線程所在類。
2、類初始化時機
主動引用(發生類初始化過程)
- new一個對象。
- 調用類的靜態成員(除了final常量)和靜態方法。
- 通過反射對類進行調用。
- 虛擬機啟動,main方法所在類被提前初始化。
- 初始化一個類,如果其父類沒有初始化,則先初始化父類。
被動引用(不會發生類的初始化)
- 當訪問一個靜態變量時,只有真正聲明這個變量的類才會初始化。(子類調用父類的靜態變量,只有父類初始化,子類不初始化)。
- 通過數組定義類引用,不會觸發此類的初始化。
- final變量不會觸發此類的初始化,因為在編譯階段就存儲在常量池中。
四、圖解分析類加載
1 public class ClassLoaderProduce { 2 static int d=3; 3 static{ 4 System.out.println("我是ClassLoaderProduce類"); 5 } 6 public static void main(String [] args){ 7 int b=0; 8 String c="hello"; 9 SimpleClass simpleClass=new SimpleClass(); 10 simpleClass.run(); 11 } 12 } 13 14 class SimpleClass{ 15 static int a=3; 16 static{ 17 a=100; 18 System.out.println(a); 19 } 20 21 public SimpleClass(){ 22 System.out.println("對類進行加載!"); 23 } 24 25 public void run(){ 26 System.out.println("我要跑跑跑!"); 27 } 28 }
步驟一:裝載ClassLoaderProduce類,在方法區生成動態數據結構(靜態變量、靜態方法、常量池、類代碼),並且在堆中生成java.lang.Class對象;然后進行鏈接
步驟二:初始化:把static{}與靜態變量合並存放在類構造器當中,對靜態變量賦值。 1-5行執行完畢。
步驟三:執行main方法,首先在棧里面生成一個main方法的棧禎,定義變量b、c,注意此處的變量b、c存儲的常量池存儲的變量的地址,如圖所示。
步驟四:創建SimpleClass對象;跟上面步驟類似:加載-鏈接-初始化。然后,調用run()方法的時候,它會通過classLoader局部變量的地址尋找到類的class對象並且調用run()方法