在講JVM最開始,我們先以一個簡單的Java程序的運行開始講,JAVA程序的運行原理。下面我先把我們實例程序列出來,我們該實例程序一共有兩個java文件:Hello.java和Person.java:
Hello.java文件:
public class Hello { public static void main(String[] args) { Person p = new Person(); }}
Person.java文件
public class Person { private String name;}
首先在IDE中編寫java源文件,文件名以.java結束。編寫完源文件,然後使用javac命令把源文件編譯成以.class結尾的二進位文件或者打包為jar文件。
當我們需要去執行我們編寫的java程序的時候,如果是直接編譯後的.class文件,那麼使用:
java com.xx.Hello.class
java *.class命令去運行包含了main方法的那個.class文件。如果我們是以jar的方式進行運行java程序,那麼執行 :
java -jar *.jar com.xx.Hello
起中 Hello是這個jar中包含了main方法的類的名稱。
無論是那種方式,當我們執行的時候,就會啟動jvm虛擬機去加載所要執行的Hello.class文件到虛擬機中。然後在jvm中有字節碼執行引擎負責去執行Hello.class中的main方法,在main方法中使用到了Person類,此時jvm又會去加載Person。也就是說jvm用到哪個類,然後就去加載哪個類。下面用圖來描述一下java程序的執行過程:
java執行過程
1.執行java命令啟動jvm,通過類加載器Hello.class加載到jvm中
2.jvm中的字節碼執行引擎執行Hello.class中入口方法main方法
3.由於main使用了Person類,然後jvm類加載器再去加載Person類。
4.最後字節碼執行引擎執行Person.class。
類的加載過程
通過上面的結束,基本了解了java程序的一個大致的執行過程,在上面描述的加載過程中,其中一個最重要的環節是類的加載。下面接受一下類是如何加載的。
在Java類被加載到虛擬機到從內存中卸載,整個生命周期包括以下部分:
加載,連接,初始化,使用,卸載
在java中的所有的類型都是在運行的過程中進行加載,連接和初始化。
類的加載過程分為三個部分:
加載:把class文件加載到內存中
連接:分為三個部分:驗證,準備,解析。
初始化:類加載最後一步,對類中的變量進行賦值,在代碼層面是就是執行用戶在類中的定義的賦值語句。
連接
其中連接又分為三個部分:
驗證: 主要是包括文件格式驗證,元數據驗證,字節碼驗證
準備: 準備階段是為類分配內存空間。
解析: 將虛擬機常量池內的符合引用替換為直接引用的過程,這部分比較複雜以後會詳細說。
#初始化
類的初始化虛擬機規範規定:只有在5種情況下才會發生:
1.執行new 對象操作、讀取活設置一個類的靜態欄位(不包括用final修飾過的,因為final修飾的屬性已經在編譯器被放到常量池中)、調用一個類的靜態方法。
2.使用reflect包的方法對類進行反射調用的時候。
3.當初始化一個類的時候,如果父類沒有初始化,先完成對父類的初始化。
4.當JVM啟動的時候,需要指定一個執行main方法的主類,那麼虛擬機會首先初始化這個主類。
5.JDK 7 中使用動態語言支持時,如果一個 java.lang.invoke.MethodHandle 實例正好是對 REF_getStatic, REF_putStatic, REF_invokeStatic 進行方法句柄解析的結果時。
>1.對於接口的初始化,對於子接口與父接口的規則與java類不一樣,子接口的初始化並不要求父接口全部都完成了初始化,只有在真正使用到父接口的時候,才會對父接口進行初始化。
2.如果接口中定義了默認實現方法,那麼當實現這個接口的類初始化的時候,也會除非這個接口的初始化。
以上五點被稱為主動使用,只有主動使用的時候,類才會被初始化。而被動使用是不會引起類的初始化的。類的被動使用又主要包括下面三種情況:
* 通過子類引用父類的靜態屬性,父類初始化,但是子類不會被初始化
* 定義某個類的數組,該類不會初始化
* 使用類修飾的final的屬性,也不會引起類初始化。