類加載:虛擬機把描述類的數據從 .class文件加載到內存,並對數據進行校驗,轉換解析和初始化,最終造成能夠被直接使用的java類型,這就是虛擬機的類加載機制。java
類的生命週期包括: android
加載(Loading)、算法
驗證(Verification)、準備(Preparation)、解析(Resolution)、數據結構
初始化(Initialization)、使用(Using)、卸載(Unloading)等七個階段,多線程
其中驗證、準備和解析三個部分統稱爲連接(Linking)。而類的加載指的就是從 加載 到 初始化 這五個階段。jvm
這七個階段的順序除了解析階段和使用階段外,其它幾個階段的開始順序是肯定的,必須按這種順序循序漸進的開始,但不要求按這種順序循序漸進的完成,這些階段一般是互相交叉地混合式進行的,一般會在一個階段執行的過程當中調用或激活另一個階段。解析階段在某些狀況下能夠在初始化階段以後再開始,以支持java的運行時綁定(RTTI),而使用階段則是按類文件內容的定義的不一樣而在不一樣的階段進行。函數
虛擬機規範對於什麼時候進行加載這一階段並無強制約束,但對於初始化階段,虛擬機規範是嚴格規定了有且只有四種狀況必須當即對類進行初始化:佈局
a、遇到new,getstatic,putstatic或invokestatic這4條字節碼指令時,若是類沒有進行過初始化,則須要先觸發其初始化。生成這4條指定的場景是:使用new關鍵字實例化對象,讀取或設置一個類的靜態字段以及調用一個類的靜態方法的時候。固然,被final修飾並在編譯期就把結果放入常量池的靜態字段不屬於這些場景,這類靜態字段的值在編譯期時就會被編譯器優化而直接放入常量池,其引用直接指向其在常量池的入口。優化
b、使用java.lang.reflect包的方法對類進行反射調用時,若是類沒有進行過初始化,則須要先觸發其初始化。this
c、當初始化一個類的時候,若是發現其父類尚未進行過初始化,則須要先觸發其父類的初始化。
d、當虛擬機啓動時,用戶須要指定一個要執行的主類(包含main()方法的類),虛擬機會先初始化這個主類。
以上四種場景中的行爲稱爲對一個類進行主動引用,除此以外全部引用類的方式都不會觸發初始化,稱爲被動引用。
接口的加載過程與類加載過程最主要的區別在於第三點,即當初始化一個接口時,並不須要先初始化其父接口,而是隻有真正使用到父接口中的字段的時候纔會初始化。
如下對類加載的各個階段進行簡單的說明。
加載階段,虛擬機須要完成三件事:經過一個類的全限定名來獲取定義此類的二進制字節流,將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構,在java堆中生成一個表明這個類的java.lang.Class對象,做爲方法區這些數據的訪問入口。
驗證階段,不一樣虛擬機會進行不一樣類驗證的實現,但大體都會完成如下四個階段的檢驗過程:文件格式驗證(驗證字節流是否符合Class文件格式的規範,並能被當前版本的虛擬機處理),元數據驗證(對字節碼描述信息進行語義分析,保證其描述信息符合java語言規範),字節碼驗證(對類方法體進行數據流和控制流分析,保證類的方法在運行時不會作出危害虛擬機的行爲)和符號引用驗證(發生在將符號引用轉化爲直接引用的時候,在解析階段中發生)。
準備階段,正式爲類成員變量(注意,不是實例成員變量,實例變量會在對象實例化時隨着對象一塊兒分配在java堆上)分配內存並設置類變量初始值(一般狀況下是數據類型的零值,不進行賦值操做)的階段,這些內存都將在方法區中進行分配。
例:public static int value=123;則在準備階段事後,value的初始值爲0而不是123,賦值指令是在初始化階段經過構造方法來執行的。
解析階段,虛擬機將常量池內的符號引用替換爲直接引用的過程。符號引用與內存佈局無關,而直接引用的目標一定已經在內存中存在。解析動做主要針對類或接口、字段、類方法、接口方法四類符號引用進行。
初始化階段,真正開始執行類中定義的java程序代碼(字節碼),是執行類構造器<clinit>()方法的過程。
<clinit>()方法的一些特色:
<clinit>()方法是由編譯器自動收集類中的全部類變量的賦值動做和靜態語句塊(static{})中的語句合併產生的,編譯器收集順序是由語句在源文件中出現的順序所決定的,靜態語句塊中只能訪問到定義在靜態語句塊以前的變量。
<clinit>()方法與類的構造函數(或者說實例構造器<init>()方法)不一樣,它不須要顯式地調用父類構造器,虛擬機會在子類的<clinit>()方法執行以前完成父類<clinit>()方法的執行。
因爲父類的<clinit>() 方法先執行,也就意味着父類中定義的靜態語句塊要優先於子類的變量賦值操做
<clinit>()方法對於類或接口來講並非必須的,若是一個類中沒有靜態語句塊,也沒有對變量的賦值操做,則編譯器能夠不爲這個類生成<clinit>()方法。
接口中不能使用靜態語句塊,但仍然有變量初始化的賦值操做,所以接口與類同樣都會生成<clinit>()方法,不一樣於類的地方是執行接口的<clinit>()方法時不坱要先執行父類的<clinit>()方法。
虛擬機會保證一個類的<clinit>()方法在多線程環境中被正確地加鎖和同步,若是多個線程同時去初始化一個類,則只有一個線程去執行這個類的<clinit>()方法,其它線程阻塞等待,直到活動線程執行<clinit>()方法完畢。
類的加載是經過ClassLoader實現的, jvm提供了三種ClassLoader的實現, 分別是:
1,BootStrap ClassLoader:稱爲啓動類加載器,它集成在jvm中,是Java類加載層次中最頂層的類加載器,負責加載JDK中的核心類庫(JAVA_HOME/jre/lib下的jar 以及 lib/classes下的類或jar等),如:rt.jar、resources.jar、charsets.jar等,能夠經過 系統屬性 sun.boot.class.path 獲得核心類庫的所有類路徑 : System.out.println(System.getProperty("sun.boot.class.path"));
另外,Bootstrap ClassLoader還負責構造Extension ClassLoader和App ClassLoader類加載器。
2,Extension ClassLoader:稱爲擴展類加載器,負責加載Java的擴展類庫,默認加載JAVA_HOME/jre/lib/ext/目下的全部jar。
3,App ClassLoader:稱爲系統類加載器,負責加載應用程序classpath目錄下的全部jar和class文件。
加載機制: 雙親委託模型(此處省略若干字..), 注意,判斷兩個類是否相同,不單要看類名,還要看是不是由同一個類加載其加載的
Java環境已經提供了三種默認的ClassLoader,可是它們只能加載指定目錄下的jar和class,若是由於某些緣由咱們想把一些類動態資源根據須要來決策什麼時候加載的話,咱們就必須定製本身的類加載器,實現一些定製的加載策略。
定製一個ClassLoader須要兩步:
一、繼承java.lang.ClassLoader
二、重寫父類的findClass方法
JDK已經在loadClass方法中幫咱們實現了ClassLoader搜索類的算法,當在loadClass方法中搜索不到類時,loadClass方法就會調用findClass方法來搜索類,因此咱們只需重寫該方法便可。如沒有特殊的要求,通常不建議重寫loadClass搜索類的算法。從loadClass的實現能夠看到雙親委託模型的過程,首先在已經加載的類中查找(查看是否已加載),若不然交給parent 進行加載,若是parent不能加載,才調用本身的findClass進行加載, 因此爲了遵照雙親委託模型,定製的classloader只須要重寫findClass 方法便可。 (android中的字節碼是通過格式優化的,不須要連接)
ClassLoader.java: /** * Loads the class with the specified name, optionally linking it after * loading. The following steps are performed: * <ol> * <li> Call {@link #findLoadedClass(String)} to determine if the requested * class has already been loaded.</li> * <li>If the class has not yet been loaded: Invoke this method on the * parent class loader.</li> * <li>If the class has still not been loaded: Call * {@link #findClass(String)} to find the class.</li> * </ol> * <p> * <strong>Note:</strong> In the Android reference implementation, the * {@code resolve} parameter is ignored; classes are never linked. * </p> * * @return the {@code Class} object. * @param className * the name of the class to look for. * @param resolve * Indicates if the class should be resolved after loading. This * parameter is ignored on the Android reference implementation; * classes are not resolved. * @throws ClassNotFoundException * if the class can not be found. */ protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { Class<?> clazz = findLoadedClass(className); if (clazz == null) { try { clazz = parent.loadClass(className, false); } catch (ClassNotFoundException e) { // Don't want to see this. } if (clazz == null) { clazz = findClass(className); } } return clazz; }
參考: