java類加載過程,瞭解一下?

先了解一下:

什麼是類的加載:jvm將class文讀取到內存中,通過對class文件的校驗、轉換解析、初始化最終在jvm的heap和方法區分配內存造成能夠被jvm直接使用的類型的過程。java

生命週期:7個階段依次爲:Loading(加載)Verification (驗證)Preparation (準備)Resolution(鏈接) Initialization (初始化)Using(使用) Unloading(卸載)c++

加載Loading

這個階段jvm完成如下動做:
首先  類加載器經過類的全路徑限定名讀取類的二進制字節流,
而後  將二進制字節流表明的類結構轉化到運行時數據區的 方法區中,
最後  在jvm堆中生成表明這個類的java.lang.Class實例(不是這個類的實例)程序員

類加載器

獲取類的二進制流 既可使用jvm自帶的類加載器,也能夠本身寫加載器來加載,這一小步是徹底可控的。不一樣的加載器能夠從各類地方讀取:zip包jar包,class文件,網絡流 。。。讀取類的二進制字節流bootstrap

同一個加載器加載的同源類纔是真的同類。不一樣加載器加載同源類,不是同類!instanceof爲FALSE安全

類加載的雙親委派模型

各個加載器都是先委託本身的父加載器加載類,若確實沒加載到再本身來加載網絡

因而java默認的類查找加載順序是自頂向下的,樹狀結構jvm

雙親委託的意圖是保證java類型體系中最基礎的行爲一致,優先加載JDK中的類模塊化

 

加載器主要有四種:函數

  • jvm啓動類加載器bootstrap loader,用c++實現爲jvm的一部分(僅指sun的hotspot),負責 JAVA_HOME/lib下面的類庫中的類的加載,這個加載器,java程序沒法引用到。url

  • 擴展類加載器Extension Loader,由sun.misc.Launcher$ExtClassLoader類實現,可在java中使用,負責JAVA_HOME/lib/ext 目錄和java.ext.dir目錄中類庫的類的加載。

  • 應用系統類加載器Application System Loader,由sun.misc.Louncher$AppClassLoader實現,負責加載用戶類路徑中類庫中的類,若是沒有使用自定義的加載器,這個就是默認的 加載器!

  • 用戶自定義加載器 本身定義從哪裏加載類的二進制流

1,類加載的過程
Java程序運行的場所是內存,當在命令行下執行:
java HelloWorld
命令的時候,JVM會將HelloWorld.class加載到內存中,並造成一個Class的對象HelloWorld.class。
其中的過程就是類加載過程:
一、尋找jre目錄,尋找jvm.dll,並初始化JVM;
二、產生一個Bootstrap Loader(啓動類加載器);
三、Bootstrap Loader自動加載Extended Loader(標準擴展類加載器),並將其父Loader設爲Bootstrap Loader。
四、Bootstrap Loader自動加載AppClass Loader(系統類加載器),並將其父Loader設爲Extended Loader。
五、最後由AppClass Loader加載HelloWorld類。
2,類加載器各自搜索的目錄
Bootstrap、 ExtClassLoader、 AppClassLoader都是類加載器,Bootstrap是本地代碼編寫的,而ExtClassLoader、 AppClassLoader都是都java編寫的,都在rt.jar中。

一、Bootstrap Loader(啓動類加載器):加載System.getProperty("sun.boot.class.path")所指定的路徑或jar。
二、Extended Loader(標準擴展類加載器ExtClassLoader):加載System.getProperty("java.ext.dirs")所指定的路徑或jar。在使用Java運行程序時,也能夠指定其搜索路徑,例如:java -Djava.ext.dirs=d:\projects\testproj\classes HelloWorld
三、AppClass Loader(系統類加載器AppClassLoader):加載System.getProperty("java.class.path")所指定的路徑或jar。在使用Java運行程序時,也能夠加上-cp來覆蓋原有的Classpath設置,例如: java -cp ./lavasoft/classes HelloWorld
 
ExtClassLoader和AppClassLoader在JVM啓動後,會在JVM中保存一份,而且在程序運行中沒法改變其搜索路徑。若是想在運行時從其餘搜索路徑加載類,就要產生新的類加載器。
3,類加載器的特色
一、運行一個程序時,老是由AppClass Loader(系統類加載器)開始加載指定的類。
二、在加載類時,每一個類加載器會將加載任務上交給其父,若是其父找不到,再由本身去加載。
三、Bootstrap Loader(啓動類加載器)是最頂級的類加載器了,其父加載器爲null.
4,類加載器的獲取
public class HelloWorld { 
        public static void main(String[] args) { 
                HelloWorld hello = new HelloWorld(); 
                Class c = hello.getClass(); 
                ClassLoader loader = c.getClassLoader(); 
                System.out.println(loader); 
                System.out.println(loader.getParent()); 
                System.out.println(loader.getParent().getParent()); 
        } 
}

運行結果:

sun.misc.Launcher$AppClassLoader@5c647e05
sun.misc.Launcher$ExtClassLoader@3d4eac69
null

從上面的結果能夠看出,並無獲取到ExtClassLoader的父Loader,緣由是Bootstrap Loader(啓動類加載器)是用C語言實現的,找不到一個肯定的返回父Loader的方式,因而就返回null。

5,類的加載
類加載有三種方式:
一、命令行啓動應用時候由JVM初始化加載
二、經過Class.forName()方法動態加載
三、經過ClassLoader.loadClass()方法動態加載
 
三種方式區別比較大,看個例子就明白了:
public class HelloWorld { 
        public static void main(String[] args) throws ClassNotFoundException { 
                ClassLoader loader = HelloWorld.class.getClassLoader(); 
                System.out.println(loader); 
                //使用ClassLoader.loadClass()來加載類,不會執行初始化塊 
                loader.loadClass("Test2"); 
                //使用Class.forName()來加載類,默認會執行初始化塊 
//                Class.forName("Test2"); 
                //使用Class.forName()來加載類,並指定ClassLoader,初始化時不執行靜態塊 
//                Class.forName("Test2", false, loader); 
        } 
}
 
public class Test2 { 
        static { 
                System.out.println("靜態初始化塊執行了!"); 
        } 
}
 
分別切換加載方式,會有不一樣的輸出結果。
6,自定義ClassLoad
爲了說明問題,先看例子:
package test; 

import java.net.MalformedURLException; 
import java.net.URL; 
import java.net.URLClassLoader; 

public class MyClassLoader { 
        public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException { 
                URL url = new URL("file:/E:\\projects\\testScanner\\out\\production\\testScanner"); 
                ClassLoader myloader = new URLClassLoader(new URL[]{url}); 
                Class c = myloader.loadClass("test.Test3"); 
                System.out.println("----------"); 
                Test3 t3 = (Test3) c.newInstance(); 
        } 
}
 
public class Test3 { 
        static { 
                System.out.println("Test3的靜態初始化塊執行了!"); 
        } 
}
 
運行後:
---------- 
Test3的靜態初始化塊執行了! 

Process finished with exit code 0
 
能夠看出自定義了ClassLoader myloader = new URLClassLoader(new URL[]{url});已經成功將類Test3加載到內存了,並經過默認構造方法構造了對象Test3 t3 = (Test3) c.newInstance();
 
有關ClassLoader還有很重要一點:
同一個ClassLoader加載的類文件,只有一個Class實例。可是,若是同一個類文件被不一樣的ClassLoader載入,則會有兩份不一樣的ClassLoader實例(前提是着兩個類加載器不能用相同的父類加載器)。

  

OSGi的網狀加載模型

雙親委派是java設計者推薦的類加載器實現方式,能夠在遵循的基礎上擴展,自定義類加載器的實現機制。

OSGi事實上的java模塊化標準,他自定義的類加載器,能不少好實現模塊化和模塊的熱部署:更換一個bundle時,連同這個bundle的類加載器一同換掉。

OSGi中java.*開頭的類按照雙親加載機制加載,而其餘類則都是由平級的類加載器加載的,造成一張網。 

驗證verification

Loading和 驗證是交叉進行的,驗證二進制字節流表明的字節碼文件是否合格,主要從一下幾方面判斷:

文件格式:參看class文件格式詳解,通過文件格式驗證以後的字節流才能進入方法區分配內存來存儲。

元數據驗證:是否符合java語言規範

字節碼驗證:數據流和控制流的分析,這一步最複雜

符號引用驗證:符號引用轉化爲直接引用時(解析階段),檢測對類自身之外的信息進行存在性、可訪問性驗證

若是確認代碼安全無誤,可用 -Xverify:none關閉大部分類的驗證,加快類加載時間

準備preparation

在方法區中給類的類變量(static修飾)分配內存

而後初始化其值,若是類變量是常量,則直接賦值爲該常量值不然爲java類型的默認的零值。

 

解析resolution

指將常量池內的符號引用替換爲直接引用的過程。

 

初始化initialization

這個階段才真正開始執行java代碼,靜態代碼塊和設置變量的初始值爲程序員設定的值

主動引用

有且只有下面5種狀況纔會當即初始化類,稱爲主動引用:

  • new 對象時

  • 讀取或設置類的靜態字段(除了 被final,已在編譯期把結果放入常量池的 靜態字段)或調用類的靜態方法時;

  • 用java.lang.reflect包的方法對類進行反射調用沒初始化過的類時

  • 初始化一個類時發現其父類沒初始化,則要先初始化其父類

  • 含main方法的那個類,jvm啓動時,須要指定一個執行主類,jvm先初始化這個類

其餘對類的引用 稱爲被動引用,加載類時不會進行初始化動做

子類繼承父類時的初始化順序

   1.首先初始化父類的static變量和塊,按出現順序

   2.初始化子類的static變量和塊,按出現順序

   3.初始化父類的普通變量,調用父類的構造函數

   4.初始化子類的普通變量,調用子類的構造函數

 

類的初始化過程發生時刻: 

1. T是一個類,當T的一個實例建立的時候,也就是T t = new T(); 

2. T的一個靜態方法被調用的時候,也就是 T.staticField(); 

3. T的靜態屬性被賦值的時候,T.staticField = o; 

4. T的一個靜態屬性被使用的時候,也就是 Object o = T.staticField; 可是它不是常量。 

5. T is a top level class , and an assert statement  lexically nested 

 

 

1,類加載的過程

Java程序運行的場所是內存,當在命令行下執行:
java HelloWorld
命令的時候,JVM會將HelloWorld.class加載到內存中,並造成一個Class的對象HelloWorld.class。
其中的過程就是類加載過程:
一、尋找jre目錄,尋找jvm.dll,並初始化JVM;
二、產生一個Bootstrap Loader(啓動類加載器);
三、Bootstrap Loader自動加載Extended Loader(標準擴展類加載器),並將其父Loader設爲Bootstrap Loader。
四、Bootstrap Loader自動加載AppClass Loader(系統類加載器),並將其父Loader設爲Extended Loader。
五、最後由AppClass Loader加載HelloWorld類。

2,類加載器各自搜索的目錄

Bootstrap、 ExtClassLoader、 AppClassLoader都是類加載器,Bootstrap是本地代碼編寫的,而ExtClassLoader、 AppClassLoader都是都java編寫的,都在rt.jar中。

一、Bootstrap Loader(啓動類加載器):加載System.getProperty("sun.boot.class.path")所指定的路徑或jar。
二、Extended Loader(標準擴展類加載器ExtClassLoader):加載System.getProperty("java.ext.dirs")所指定的路徑或jar。在使用Java運行程序時,也能夠指定其搜索路徑,例如:java -Djava.ext.dirs=d:\projects\testproj\classes HelloWorld
三、AppClass Loader(系統類加載器AppClassLoader):加載System.getProperty("java.class.path")所指定的路徑或jar。在使用Java運行程序時,也能夠加上-cp來覆蓋原有的Classpath設置,例如: java -cp ./lavasoft/classes HelloWorld
 
ExtClassLoader和AppClassLoader在JVM啓動後,會在JVM中保存一份,而且在程序運行中沒法改變其搜索路徑。若是想在運行時從其餘搜索路徑加載類,就要產生新的類加載器。

3,類加載器的特色

一、運行一個程序時,老是由AppClass Loader(系統類加載器)開始加載指定的類。
二、在加載類時,每一個類加載器會將加載任務上交給其父,若是其父找不到,再由本身去加載。
三、Bootstrap Loader(啓動類加載器)是最頂級的類加載器了,其父加載器爲null.

4,類加載器的獲取

public class HelloWorld { 
        public static void main(String[] args) { 
                HelloWorld hello = new HelloWorld(); 
                Class c = hello.getClass(); 
                ClassLoader loader = c.getClassLoader(); 
                System.out.println(loader); 
                System.out.println(loader.getParent()); 
                System.out.println(loader.getParent().getParent()); 
        } 
}

運行結果:

sun.misc.Launcher$AppClassLoader@5c647e05
sun.misc.Launcher$ExtClassLoader@3d4eac69
null

從上面的結果能夠看出,並無獲取到ExtClassLoader的父Loader,緣由是Bootstrap Loader(啓動類加載器)是用C語言實現的,找不到一個肯定的返回父Loader的方式,因而就返回null。

5,類的加載

類加載有三種方式:
一、命令行啓動應用時候由JVM初始化加載
二、經過Class.forName()方法動態加載
三、經過ClassLoader.loadClass()方法動態加載
 
三種方式區別比較大,看個例子就明白了:
public  class HelloWorld { 
         public  static  void main(String[] args)  throws ClassNotFoundException { 
                ClassLoader loader = HelloWorld. class.getClassLoader(); 
                System.out.println(loader); 
                 //使用ClassLoader.loadClass()來加載類,不會執行初始化塊 
                loader.loadClass( "Test2"); 
                 //使用Class.forName()來加載類,默認會執行初始化塊 
//                Class.forName("Test2"); 
                 //使用Class.forName()來加載類,並指定ClassLoader,初始化時不執行靜態塊 
//                Class.forName("Test2", false, loader); 
        } 
}
 
public  class Test2 { 
         static { 
                System.out.println( "靜態初始化塊執行了!"); 
        } 
}
 
分別切換加載方式,會有不一樣的輸出結果。

6,自定義ClassLoad

爲了說明問題,先看例子:
package test; 

import java.net.MalformedURLException; 
import java.net.URL; 
import java.net.URLClassLoader; 

public  class MyClassLoader { 
         public  static  void main(String[] args)  throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException { 
                URL url =  new URL( "file:/E:\\projects\\testScanner\\out\\production\\testScanner"); 
                ClassLoader myloader =  new URLClassLoader( new URL[]{url}); 
                Class c = myloader.loadClass( "test.Test3"); 
                System.out.println( "----------"); 
                Test3 t3 = (Test3) c.newInstance(); 
        } 
}
 
public  class Test3 { 
         static { 
                System.out.println( "Test3的靜態初始化塊執行了!"); 
        } 
}
 
運行後:
---------- 
Test3的靜態初始化塊執行了! 

Process finished with exit code 0
 
能夠看出自定義了ClassLoader myloader = new URLClassLoader(new URL[]{url});已經成功將類Test3加載到內存了,並經過默認構造方法構造了對象Test3 t3 = (Test3) c.newInstance();
 
有關ClassLoader還有很重要一點:
同一個ClassLoader加載的類文件,只有一個Class實例。可是,若是同一個類文件被不一樣的ClassLoader載入,則會有兩份不一樣的ClassLoader實例(前提是着兩個類加載器不能用相同的父類加載器)。
相關文章
相關標籤/搜索