大的來講,當啓動一個JAVA程序時,一個JVM即啓動了,當程度退出時,JVM也隨之消亡。
java
程序退出指:1. 全部的非daemon線程都終止了 設計模式
2. 某個線程調用了類Runtime或者System的exit方法數組
當同時啓動多個JAVA程序時,即啓動多個JVM,每一個JAVA程序都運行於本身的JVM中。
安全
一個JVM表現爲一個進程,如我同時啓動了3個JAVA程序,有3個javaw的進程網絡
JVM的是經過main()函數做爲入口啓動的。數據結構
類的生命週期dom
當咱們編寫一個java的源文件後,通過編譯會生成一個後綴名爲class的文件,這種文件叫作字節碼文件,只有這種字節碼文件纔可以在java虛擬機中運行,java類的生命週期就是指一個class文件從加載到卸載的全過程jvm
一個java類的完整的生命週期會經歷加載、鏈接、初始化、使用、和卸載五個階段,固然也有在加載或者鏈接以後沒有被初始化就直接被使用的狀況,如圖所示函數
下面依次講解
spa
1. 裝載:
類的加載就是把類的.class文件中的二進制數據讀入到內存中。把它存放在java運行時數據區的方法區內,而後在堆區建立一個java.lang.Class對象,用來封裝類在方法區內的數據結構,並做爲方法區中這個類的信息的入口。
加載來源:
經常使用的:1.本地加載,根據類的全路徑名找到相應的class文件,而後從class文件中讀取文件內容
2.從jar文件中讀取
還有:1.從網絡中獲取,好比10年前十分流行的Applet。
2.根據必定的規則實時生成,好比設計模式中的動態代理模式,就是根據相應的類自動生成它的代理類。
3.從非class文件中獲取,其實這與直接從class文件中獲取的方式本質上是同樣的,這些非class文件在jvm中運行以前會被轉換爲可被jvm所識別的字節碼文件。
加載方式:
類的加載是由加載器完成的,可分爲兩種:
1。 java虛擬機自帶的加載器,包括啓動類加載器,拓展類加載器和系統類加載器。
2。 用戶自定義的類加載器,是java.lang.ClassLoader類的子類的實例。用戶能夠經過它來制定類的加載器。
加載時機:
對於加載的時機,各個虛擬機的作法並不同,可是有一個原則,就是當jvm「預期」到一個類將要被使用時,就會在使用它以前對這個類進行加載。好比說,在一段代碼中出現了一個類的名字,jvm在執行這段代碼以前並不能肯定這個類是否會被使用到,因而,有些jvm會在執行前就加載這個類,而有些則在真正須要用的時候纔會去加載它,這取決於具體的jvm實現。咱們經常使用的hotspot虛擬機是採用的後者,就是說當真正用到一個類的時候纔對它進行加載。
加載階段是類的生命週期中的第一個階段,加載階段以後,是鏈接階段。
2. 鏈接:
這個階段的主要任務就是作一些加載後的驗證工做以及一些初始化前的準備工做,能夠細分爲三個步驟:驗證、準備和解析。有一點須要注意,有時鏈接階段並不會等加載階段徹底完成以後纔開始,而是交叉進行,可能一個類只加載了一部分以後,鏈接階段就已經開始了。可是這兩個階段總的開始時間和完成時間老是固定的:加載階段老是在鏈接階段以前開始,鏈接階段老是在加載階段完成以後完成。
驗證:保證被加載的類有正確的內部結構,而且與其餘類協調一致。若是jvm檢查到錯誤,就會拋出相應的Error對象。
類驗證的內容:
1。 類文件的結構檢查,確保類文件聽從java類文件的固定格式。
2。 語義檢查確保自己符號java語言的語法規定,如驗證final類型的類沒有子類等
3。 字節碼驗證:確保字節碼能夠被jvm安全的執行
4 。二進制兼容的驗證,確保相互引用的之間協調一致,如Worker類的goWork方法會調用Car類的run方法,jvm在驗證Worker類時,會檢查方法區內是否存在car類的run方法,如不存在會拋出NoSuchMethodError錯誤。
-----疑問:由java編譯器生成的java類的二進制數據確定是正確的,爲何還要進行類的驗證? 由於java虛擬機並不知道某個特定的.class文件究竟是如何被建立的,這個.class文件有多是由正常的java編譯器生成的,也多是由黑客特製的,黑客視圖經過它來破壞jvm環境。類的驗證能提升程序的健壯性,確保程序被安全的執行。
準備:爲類的靜態變量分配內存並設爲jvm默認的初值,對於非靜態的變量,則不會爲它們分配內存。注意,靜態變量的初值爲jvm默認的初值,而不是咱們在程序中設定的初值。jvm默認的初值是這樣的:
* 基本類型(int、long、short、char、byte、boolean、float、double)的默認值爲0。
* 引用類型的默認值爲null。
* 常量的默認值爲咱們程序中設定的值,如咱們定義final static int a = 100,則準備階段中a的初值就是100。
解析:把常量池中的符號引用轉換爲直接引用。
符合引用?直接引用? 在Worker類的gotowork方法中會引用Car類run方法。
public void gotowork() { car.run();//這段代碼在Worker類的二進制數據中表示爲符號引用 }
在Worker類的二進制數據中,包含了一個對Car類的run方法的的符號引用,它有run方法的全名和相關描述符號組成,在解析階段jvm會把這個符號引用替換爲一個指針,該指針指向Car類的run方法在方法區裏的內存地址,這個指針就是直接引用,car.run()就是符合引用。
鏈接階段完成以後會根據使用的狀況(直接引用仍是被動引用)來選擇是否對類進行初始化
3. 初始化:
若是一個類被直接引用,就會觸發類的初始化。在java中,直接引用的狀況有:
1. 經過new關鍵字實例化對象,訪問或設置類的靜態變量,調用類的靜態方法。
2. 經過反射方式執行以上三種行爲。
3. 初始化子類的時候,會觸發父類的初始化。
4. 做爲程序入口直接運行時(也就是直接調用main方法)。
除了以上四種狀況,其餘使用類的方式叫作被動引用,而被動引用不會觸發類的初始化。請看主動引用的示例代碼:
import java.lang.reflect.Field; import java.lang.reflect.Method; class InitClass{ static { System.out.println("初始化InitClass"); } public static String a = null; public static void method(){} } class SubInitClass extends InitClass{} public class Test1 { /** * 主動引用引發類的初始化的第四種狀況就是運行Test1的main方法時 * 致使Test1初始化,這一點很好理解,就不特別演示了。 * 本代碼演示了前三種狀況,如下代碼都會引發InitClass的初始化, * 但因爲初始化只會進行一次,運行時請將註解去掉,依次運行查看結果。 * @param args * @throws Exception */ public static void main(String[] args) throws Exception{ // 主動引用引發類的初始化一: new對象、讀取或設置類的靜態變量、調用類的靜態方法。 // new InitClass(); // InitClass.a = ""; // String a = InitClass.a; // InitClass.method(); // 主動引用引發類的初始化二:經過反射實例化對象、讀取或設置類的靜態變量、調用類的靜態方法。 // Class cls = InitClass.class; // cls.newInstance(); // Field f = cls.getDeclaredField("a"); // f.get(null); // f.set(null, "s"); // Method md = cls.getDeclaredMethod("method"); // md.invoke(null, null); // 主動引用引發類的初始化三:實例化子類,引發父類初始化。 // new SubInitClass(); } }
上面的程序演示了主動引用觸發類的初始化的四種狀況。
在類的初始化階段,只會初始化與類相關的靜態賦值語句和靜態代碼塊,也就是有static關鍵字修飾的信息,而沒有static修飾的賦值語句和執行語句在實例化對象的時候纔會運行。
在初始化靜態賦值語句和靜態代碼塊時,是按順序執行的。若是有父類則先初始化父類的。
順序執行指,不論是賦值語句仍是代碼塊,誰在前就先初始化誰
class InitClass{ // 1 static{ // 2 System.out.println("運行父類靜態代碼"); // 3 } // 4 public static Field1 f1 = new Field1(); // 5 public static Field1 f2; // 6 }
上面初始化順序爲 3, 5。 6不會初始化,由於其是聲明操做,沒賦值。
class InitClass{ // 1 public static Field1 f1 = new Field1(); // 2 public static Field1 f2; // 3 static{ // 4 System.out.println("運行父類靜態代碼"); // 5 } // 6 }
上面初始化順序爲 2, 5。
4. 使用:
類的使用包括主動引用和被動引用,主動引用在初始化的章節中已經說過了,下面咱們主要來講一下被動引用:
1. 引用父類的靜態字段,只會引發父類的初始化,而不會引發子類的初始化。
2. 定義類數組,不會引發類的初始化。
3. 引用類的常量(final,其在加載的準備階段就賦值好了),不會引發類的初始化。
4. 調用ClassLoader類的loadClass()方法加載一個類,並非對類的主動引用,不會致使類的初始化。當程序調用Class類的靜態方法forName("ClassA")時,纔是對ClassA的主動使用,將致使classA被初始化,他的靜態代碼塊被執行
見
class InitClass{ static { System.out.println("初始化InitClass"); } public static String a = null; public final static String b = "b"; public final static int c =(int)Math.random();// 非編譯時常量 public static void method(){} } class SubInitClass extends InitClass{ static { System.out.println("初始化SubInitClass"); } } public class Test4 { public static void main(String[] args) throws Exception{ // String a = SubInitClass.a;// 引用父類的靜態字段,只會引發父類初始化,而不會引發子類的初始化 // String b = InitClass.b;// 使用類的常量不會引發類的初始化,由於在加載解析時就把InitClass.b解析爲常量值了. // int c = InitClass.c; // 此處也是類的常量,可是其在運行期才知道,因此是會觸發初始化的。但無論在什麼階段初始化,final標示的變量在初始化後就不會變,如不是final的,每次new都會變 SubInitClass[] sc = new SubInitClass[10];// 定義類數組不會引發類的初始化 ClassLoader loader = ClassLoader.getSystemClassLoader();// 得到系統的類加載器 System.out.println("系統類加載器:" + loader); Class objClass = loader.loadClass("InitClass"); // 此處不會初始化InitClass objClass = Class.forName("InitClass");// 此處纔會初始化InitClass } }
最後總結一下使用階段:使用階段包括主動引用和被動引用,主動飲用會引發類的初始化,而被動引用不會引發類的初始化。
5. 卸載:
在類使用完以後,若是知足下面的狀況,類就會被卸載:
1. 該類全部的實例都已經被回收,也就是java堆中不存在該類的任何實例。
2. 加載該類的ClassLoader已經被回收。
3. 該類對應的java.lang.Class對象沒有任何地方被引用,沒法在任何地方經過反射訪問該類的方法。
若是以上三個條件所有知足,jvm就會在方法區垃圾回收的時候對類進行卸載,類的卸載過程其實就是在方法區中清空類信息,java類的整個生命週期就結束了。
以上是類的整個生命週期。