深入理解Java中JVM對類的加載機制


有如下經典代碼:

  1. package test;
  2. class Singleton {     
  3. public static Singleton singleton = new Singleton();     
  4. public static int a;     
  5. public static int b = 0;     
  6. private Singleton() {     
  7. super();     
  8.         a++;     
  9.         b++;     
  10.     }     
  11. public static Singleton GetInstence() {     
  12. return singleton;     
  13.     }     
  14. }     
  15. public class MyTest {    
  16. public static void main(String[] args) {     
  17.         Singleton mysingleton = Singleton.GetInstence();     
  18.         System.out.println("a="+mysingleton.a);     
  19.         System.out.println("b="+mysingleton.b);     
  20.     }     
  21. }    

運行結果為:a=1,b=0;

如果將代碼加以修改,將原來的3-5行代碼如下:

public static Singleton singleton = new Singleton();     

public static int a;     

public static int b = 0;

修改為

public static int a;     

public static int b = 0;     

public static Singleton singleton = new Singleton();

運行程序,結果為a=1,b=1

為什么改變了成員變量的順序,運行結果會有所不同?為了弄清這個問題,就必須先理解Java中JVM加載類的機制。

java程序運行需要使用某個類時,如果該類還沒有加載到內存中,系統會通過加載、連接、初始化三個步驟來對該類進行初始化。如下圖所示:

 

 

1.類加載
當我們運行java.exe命令執行某個java程序時,由於java程序本身以.class字節碼的形式存在,它不是一個可執行文件,所以需要JVM將類文件加載到內存中。
JVM中有三個類加載器:根類加載器、擴展類加載器和系統類加載器(也叫應用加載器)。根類加載器是C++實現的,擴展類加載器和應用加載器都是java語言實現的。
當運行java.exe命令執行一個java程序時,程序最基本的加載流程如下:
1.java.exe程序搜索jre目錄,尋找JVM.dll,啟動JVM
2.JVM運行根類加載器,加載java核心類(所有java.*開頭的類)
3,根類加載器運行后,它會自動加載擴展類加載器和系統類加載器,並將擴展類加載器的父類設置為根類加載器,將應用加載器的父類設置為擴展累加載器。
4.擴展類加載器搜索jre/lib/ext目錄,加載擴展API。
5.應用加載器搜索CLASSPATH目錄,加載我們要運行的類。
6.類的class文件讀入內存后,就會創建一個java.lang.Class對象,一旦某個類被載入JVM中,同一個類就不會再次被載入
一個類加載后,對應的Class對象,可以通過該類的實例的getClass()方法獲得,Class對象有一個getClassLoader()方法,可以得到加載該類所用的類加載器。
2.連接
當類被加載后,系統就會位置創建一個對應的Class對象,接着進入連接階段,連接又分為以下三個階段:
1.驗證:檢驗被加載的類是否有正確的內部結構,並和其他類協調一致。
2.准備:負責為類的靜態屬性分配內存,並設置默認初始值。注意:必須是靜態屬性!因為此時並不存在實例對象,設置值也是默認值初始值,而不是人為給定的值
3.解析:將類的二進制數據中的符號引用替換成直接引用。
3.初始化
JVM負責對類進行初始化,也就是對靜態屬性進行初始化。java中對靜態屬性指定初始值的方式有兩種:①聲明靜態屬性時指定初始值;②使用靜態初始化快為靜態屬性指定初始值。
需要注意的是JVM對一個類初始化時如果該類的父類沒有被初始化,則會先初始化其父類,如果直接父類還有父類,那么會先初始化父類的父類,以此類推。所以,JVM最先初始化的總是java.lang.Object類,
當程序主動使用任何一個類時,系統會保證該類以及它的所有父類都會被初始化。
當java程序首次通過下面的六種方式來使用某個類或者接口時,系統就會初始化該類或者接口:
♦創建類的實例
調用某個類的靜態方法
訪問某個類或接口的靜態屬性,或者為靜態屬性賦值
使用反射方式強制創建某個類或接口對應的java.lang.Class對象
初始化某個類的子類
直接用java.exe命令運行某個主類。

回顧那個詭異的代碼

 

從入口開始看

Singleton mysingleton = Singleton.GetInstence();

是根據內部類的靜態方法要一個Singleton實例。這個時候就屬於主動調用Singleton類了。之后內存開始加載Singleton類

1):對Singleton的所有的靜態變量分配空間,賦默認的值,所以在這個時候,singleton=null、a=0、b=0。注意b=0是默認值,並不是我們手工為其賦予的的那個0值。(這一步對應連接階段)

2):之后對靜態變量賦初始值,這個時候的賦值就是我們在程序里手工初始化的那個值了。(這一步對應初始化階段)

按照順序,首先初始化靜態變量singleton = new Singleton();調用了構造方法。構造方法里面a=1、b=1。之后接着順序往下執行。

接下來public static int a;為a賦初始值,因為沒有指定初始值,所以a保持不變,即a=1,

再之后public static int b = 0;為b賦初始值,代碼中指定的初始值為0,此時b之前的1被0覆蓋,因而b=0;  所以最終結果為a=1,b=0。

而如果將代碼中靜態屬性的聲明順序改為如下:

public static int a;    

public static int b = 0;     

public static Singleton singleton = new Singleton();

那么先初始化a,a沒有指定初始值,保持不變,a仍為默認值0,再初始化b,b的指定初始值為0,所以b=0,

最后初始化Singleton singleton = new Singleton();調用構造器,其中a++,b++,所以最終結果為a=1,b=1

 
 

                        
                        
                 

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com