1 類的加載
1 概念:類的加載就是把類的.class文件中的二進制數據讀入到內存中。把它存放在java運行時數據區的方法區內,然後在堆區創建一個java.lang.Class對象,用來封裝類在方法區內的數據結構。
2 來源: JVM可以從多種來源加載類的class德二進制數據
1 從本地文件系統加載類的class文件
2 從網絡上下載類的class的二進制文件.
3 從zip文件或jar文件或其他歸檔文件中提取.class文件。
4 從一個專有的數據庫中提取class文件.
5 把一個java源文件動態編譯爲class文件。
類加載的最終產品是位於堆區的java.lang.Class對象。Class對象封裝了類在方法區的數據結構。並且向java程序提供了訪問類在方法區的數據結構的接口。
類的加載是由加載器完成的,可分爲兩種:
1 java虛擬機自帶的加載器,包括啓動類加載器,拓展類加載器和系統類加載器。
2 用戶自定義的類加載器,是java.lang.ClassLoader類的子類的實例。用戶可以通過它來制定類的加載器。
類的加載器並不需要等到某個類被首次主動使用時再加載它,java虛擬機規範允許類加載器在預料某個類將要被使用時就預先加載它。
2 類的驗證
當類別加載後,就進入連接階段。連接就是把已經讀入到內存的類的二進制數據合併到虛擬機的運行環境去。連接的第一步是類的驗證,保證被加載的類有正確的內部結構,並且與其他類協調一致。如果jvm檢查到錯誤,那麼就會拋出相應的Error對象。由java編譯器生成的java類的二進制數據肯定是正確的,爲什麼還要進行類的驗證,因爲java虛擬機並不知道某個特定的.class文件到底是如何被創建的,這個.class文件有可能是由正常的java編譯器生成的,也可能是由黑客特製的,黑客視圖通過它來破壞jvm環境。類的驗證能提高程序的健壯性,確保程序被安全的執行。
類驗證的內容:
1 類文件的結構檢查,確保類文件遵從java類文件的固定格式。
2 語義檢查確保本身符號java語言的語法規定,如驗證final類型的類沒有子類等
3 字節碼驗證:確保字節碼可以被jvm安全的執行,字節碼代表java方法(包括靜態和實例方法),它是由被稱做操作碼的單字節指令組成的序列,每一個操作碼後都跟着一個或多個操作數。字節碼驗證步驟會檢查每個操作碼是否合法,即是否有合法的操作數。
4 二進制兼容的驗證,確保相互引用的之間協調一致,如Worker類的goWork方法會調用Car類的run方法,jvm在驗證Worker類時,會檢查方法區內是否存在car類的run方法,如不存在會(Worker 類和car類版本不兼容就會出現問題),就會拋出NoSuchMethodError錯誤。
3 類的準備:在準備階段,jvm虛擬機爲類的靜態變量分配內存,並設置默認的初始值。如int初始化值是0佔是2個字節等。
4 類的解析:在解析階段,jvm會把類的二進制數據中的符號引用替換爲直接引用。在Worker類的gotowork方法中會引用Car類run方法。
- public void gotowork()
- {
- car.run();//這段代碼在Worker類的二進制數據中表示爲符號引用
- }
public void gotowork(){car.run();//這段代碼在Worker類的二進制數據中表示爲符號引用}
在Worker類的二進制數據中,包含了一個對Car類的run方法的的符號引用,它有run方法的全名和相關描述符號組成,在解析階段jvm會把這個符號引用替換爲一個指針,該指針指向Car類的run方法在方法區德內存地址,這個指針就是直接引用。
5 類的初始化:
在初始化階段,jvm執行類的初始化語句,爲類的靜態變量賦予初始值。在程序中靜態變量的初始化有兩種途徑:(1) 在靜態變量的聲明處進行初始化,(2)在靜態代碼塊進行初始化。jvm會按照他們的先後順序進行初始化。
jvm初始化一個類包含的步驟:
1 如果累沒有加載和連接,那就先進行加載和鏈接。
2 假如類存在直接的父類,並且這個類的父類沒有初始化,那就先初始化直接的父類。
3 假如類中存在初始化語句,那就依次執行這些初始化語句。
當初始化一個類的直接父類時,也需要重複以上步驟,這會確保程序主動使用一個類時,這個類及所有的父類和間接父類都已初始化。
- package init;
- public class Base {
- static int a = 1;
- static {
- System.out.println("init Base");
- }
- public Base() {
- System.out.println("Base()");
- }
- }
- public class Sub extends Base {
- static int b = 1;
- static {
- System.out.println("init Sub");
- }
- public Sub() {
- System.out.println("Sub()");
- }
- }
- public class InitTest {
- static {
- System.out.println("init InitTest");
- }
- public static void main(String[] args) throws Exception {
- // System.out.println("b="+Sub.b);
- // init InitTest
- // init Base
- // init Sub
- // b=1
- // Base b;//不會初始化Base類
- // b=new Base();
- // System.out.println("創建一個Base類的實例");
- // System.out.println(b.a);
- // System.out.println("b=" + Sub.b);//沒有初始化Base類 僅僅初始化Sub類
- // System.out.println("創建一個Sub");
- // new Sub();
- }
- }
package init;public class Base {static int a = 1;static {System.out.println("init Base");}public Base() {System.out.println("Base()");}}public class Sub extends Base {static int b = 1;static {System.out.println("init Sub");}public Sub() {System.out.println("Sub()");}}public class InitTest {static {System.out.println("init InitTest");}public static void main(String[] args) throws Exception {// System.out.println("b="+Sub.b);// init InitTest// init Base// init Sub// b=1// Base b;//不會初始化Base類// b=new Base();// System.out.println("創建一個Base類的實例");// System.out.println(b.a);// System.out.println("b=" + Sub.b);//沒有初始化Base類 僅僅初始化Sub類// System.out.println("創建一個Sub");// new Sub();}}
6 類的初始化時機:
1 創建類的實例,包括用new創建實例,或者通過反射,克隆和反序列號來創建實例。
2 調用類的靜態方法。
3 訪問某個類的或接口的靜態變量或者對該靜態變量賦值。
4 調用java api中某些反射方法如Class.forName("Worker"),如果Worker類沒有初始化,那麼forname方法會初始化Worker類,然後返回這個Worker類的class實例。
5 初始化類的一個子類,例如初始化一個Sub類,可以看做是對它父類Base類的主動使用,先初始化Base類。
7 jvm啓動時被表明爲啓動類的類,如含有main方法的類
以下使用java類的方式被看做是對類的被動使用都不會導致類的初始化。
1 對final類類型的靜態變量如果在編譯時期就能計算出變量的取值,那麼這種變量被看做編譯時常量。java程序對類的編譯時常量的使用,不會導致類的初始化。
- public class Tester{
- public final static int a=3;// 編譯時常量
- public final static int b =(int)Math.random();// 非編譯時常量
- }
public class Tester{public final static int a=3;// 編譯時常量public final static int b =(int)Math.random();// 非編譯時常量}
當另外一個類使用Tester.a時,不會在使用方法的字節碼中保存一個表示Tester.a的符號的引用,也不會分配內存。而是直接在字節碼中嵌入常量值。使用Test.b時,看做是對類的主動使用,會進行類的初始化。
2 在jvm虛擬機初始化一個類時,要求它的所有父類都已經被初始化,但是不適用於接口,在初始化一個類是,並不會先初始化他所實現的接口。
在初始化一個接口時,並不會先初始化他的父接口。一個父接口並不會因爲它的子接口或實現類初始化而初始化,只要當程序首次使用特定接口的靜態變量時,纔會導致該接口的初始化。
只有當程序訪問的靜態變量和靜態方法的確在當前類或接口中定義是,纔可以看做是對類或接口的主動使用。
- package initbase;
- class Base {
- static int a = 32;
- static {
- System.out.println("初始化Base");
- }
- public static void methodA() {
- System.out.println("method of Base");
- }
- }
- class Sub extends Base {
- static {
- System.out.println("init Base");
- }
- }
- public class Sample {
- public static void main(String[] args) {
- System.out.println(Sub.a);// 僅僅初始化Base
- Sub.methodA();
- }
- }
package initbase;class Base { static int a = 32; static { System.out.println("初始化Base"); } public static void methodA() { System.out.println("method of Base"); }}class Sub extends Base { static { System.out.println("init Base"); }}public class Sample { public static void main(String[] args) { System.out.println(Sub.a);// 僅僅初始化Base Sub.methodA(); }}
3 調用ClassLoader類的loadClass()方法加載一個類,並不是對類的主動使用,不會導致類的初始化。當程序調用Class類的靜態方法forName("ClassA")時,纔是對ClassA的主動使用,將導致classA被初始化,他的靜態代碼塊被執行。
- package loaderinit;
- class ClassA {
- static {
- System.out.println("now init ClassA");
- }
- }
- public class Tester {
- public static void main(String[] args) throws Exception {
- ClassLoader loader = ClassLoader.getSystemClassLoader();// 獲得系統的類加載器
- System.out.println("系統類加載器:" + loader);
- Class objClass = loader.loadClass("loaderinit.ClassA");
- System.out.println("after load ClassA");
- System.out.println("before init Class");
- objClass = Class.forName("loaderinit.ClassA");// 初始化ClassA
- }
- }
package loaderinit;class ClassA {static {System.out.println("now init ClassA");}}public class Tester {public static void main(String[] args) throws Exception {ClassLoader loader = ClassLoader.getSystemClassLoader();// 獲得系統的類加載器System.out.println("系統類加載器:" + loader);Class objClass = loader.loadClass("loaderinit.ClassA");System.out.println("after load ClassA");System.out.println("before init Class");objClass = Class.forName("loaderinit.ClassA");// 初始化ClassA}}
類加載器:
類加載器用來把類加載到jvm中,從jdk1.2開始類的加載過程採用父親委託機制,能保證java平臺的安全。jvm自帶的根加載器以外,其餘的類加載器有且只有一個父加載器。java虛擬機自帶的幾種加載器:
根(Bootstrap)類加載器:該類沒有父加載器,負責加載虛擬機的核心類庫,如java.lang.*等。根類加載器從系統屬性sun.boot.class.path所指定的目錄中加載類庫,根加載器的實現依賴於底層操作系統,屬於虛擬機的實現的一部分,沒有繼承java.lang.ClassLoader。
拓展(Extension)類加載器:它的父加載器爲根加載器,它從java.ext.dirs系統屬性所指定的目錄中加載類庫,或者從jdk的安裝目錄的jre\lib\ext子目錄下加載類庫。如用戶把用戶創建的jar文件放在這個目錄下也會自動由拓展類加載器加載,拓展類加載器是純java類,是java.lang.ClassLoader類的子類。
系統(System)類加載器:也稱爲應用類加載器,它的父類加載器爲拓展類加載器。它從環境變量classpath或者系統屬性java.class.path所指定的目錄中加載類。它是用戶自定義的類加載器的默認父加載器。也是純java類 ,java.lang.ClassLoader的子類。
自定義類加載器:繼承抽象類java.lang.ClassLoader.
- package classloader;
- public class Example {
- /**
- * @param args
- */
- public static void main(String[] args) {
- Class c = null;
- ClassLoader loader1, loader2;
- // 獲得系統類加載器
- loader1 = ClassLoader.getSystemClassLoader();
- System.out.println("系統類加載器:" + loader1);
- while (loader1 != null) {
- loader2 = loader1;
- loader1 = loader1.getParent();
- System.out.println(loader2 + "的父親加載器是:" + loader1);
- }
- try {
- c = Class.forName("java.lang.Object");
- loader1 = c.getClassLoader();
- System.out.println("java.lang.Object 的加載器:" + loader1);// 根(Bootstrap)加載器
- c = Class.forName("classloader.Example");
- loader1 = c.getClassLoader();
- System.out.println("classloader.Example 的加載器:" + loader1);// 是根(Bootstrap)加載器
- System.out.println(Thread.currentThread().getContextClassLoader());
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- /**
- *
- * 系統類加載器:sun.misc.Launcher$AppClassLoader@19821f
- * sun.misc.Launcher$AppClassLoader@19821f的父親加載器是:sun.misc.Launcher$ExtClassLoader@addbf1
- * //拓展類加載器 sun.misc.Launcher$ExtClassLoader@addbf1的父親加載器是:null
- * java.lang.Object 的加載器:null //根加載器 classloader.Example
- * 的加載器:sun.misc.Launcher$AppClassLoader@19821f
- * sun.misc.Launcher$AppClassLoader@19821f //系統類加載器
- *
- */
- }