Java類加載

java虛擬機與程序的生命週期

在以下幾種狀況下,java虛擬機將結束生命週期java

  • 執行了System.exit()方法
  • 程序正常執行結束
  • 程序在執行過程當中遇到了異常或錯誤而異常終止
  • 因爲操做系統出現錯誤而致使java虛擬機進程終止

類的加載,鏈接與初始化

  • 加載:查找並加載類的二進制數據
  • 鏈接
    • 驗證:保證被加載的類的正確性
    • 準備:爲類的靜態變量分配內存,並將其初始化爲默認值
    • 解析:把類中的符號引用轉換爲直接引用
  • 初始化:爲類的靜態變量賦予正確的初始值

java程序對類的使用方式可分爲兩種

  • 主動使用
  • 被動使用
    全部的java虛擬機實現必須在每一個類或接口被java程序「首次主動使用」時才初始化它們

主動使用(六種)

  • 建立類的實例
  • 訪問某個類或接口的靜態變量,或者對該靜態變量賦值
  • 調用類的靜態方法
  • 反射(如Class.forName("com.ak.test"))
  • 初始化一個類的子類
  • java虛擬機啓動時被標明爲啓動類的類(java test)
    除了以上6種狀況,其餘使用java類的方式都被看做是對類的被動使用,都不會致使類的初始化

類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區,而後在
堆區建立一個java.lang.Class對象,用來封裝類在方法區內的數據結構程序員

加載class文件的方式

  • 從本地系統中直接加載
  • 經過網絡下載.class文件
  • 從zip,jar等歸檔文件中加載.class文件
  • 從專有數據庫中提取.class文件
  • 將java源文件動態編譯爲.class文件

類的加載的最終產品是位於堆區中的Class對象
Class對象封裝了類在方法區內的數據結構,而且向java程序員提供了訪問方法區內的數據結構的接口數據庫

類的加載

有兩種類型的類加載器安全

  • java虛擬機自帶的加載器
    • 根類加載器(Bootstrap)
    • 擴展類加載器(Extension)
    • 系統類加載器(System)
  • 用戶自定義的類加載器
    • java.lang.ClassLoader的子類
    • 用戶能夠定製類的加載方式
public class ClassLoaderDemo {
    public static void main(String[] args) {
        ClassLoader loader =  ClassLoaderDemo.class.getClassLoader();
        while(loader!=null){
            System.out.println(loader.getClass().getName());
            loader = loader.getParent();
        }
        System.out.println(String.class.getClassLoader());
    }
}
  • 類加載器並不須要等到某個類被「首次主動使用」時再加載它
  • jvm規範容許類加載器在預料某個類將要被使用時就預先加載它,若是在預先加載的過程當中遇到了.class文件缺失或存在
    錯誤,類加載器必須在程序首次主動使用該類時才報告錯誤(LinkageError錯誤)
  • 若是這個類一直沒有被程序主動使用,那麼類加載器就不會報告錯誤

類的驗證

類被加載後,就進入鏈接階段,鏈接就是將已經讀入到內存的類的二進制數據合併到虛擬機的運行時環境中去
類的驗證內容網絡

  • 類文件的結構檢查
    確保類文件聽從java類文件的固定格式
  • 語義檢查
    確保類自己符合java語言的語法規定,好比驗證final類型的類沒有子類,以及final類型的方法沒有覆蓋
  • 字節碼驗證
    確保字節碼流能夠被java虛擬機安全的執行,字節碼流表明java方法(包括靜態方法和實例方法),它是由被稱作操做碼的單字節指令
    組成的序列,每個操做碼後都跟着一個或多個操做數,字節碼驗證步驟會檢查每一個操做碼是否合法,便是否有着合法的操做數
  • 二進制兼容性驗證
    確保相互引用的類之間協調一致,例如在Worker類的gotoWork()方法中會調用Car類的run()方法,java虛擬機在驗證Worker類時會
    檢查在方法區內是否存在Car類的run()方法,假如不存在(當Worker類和Car類的版本不兼容,就會出現這種問題)會拋出NoSuchMethodError錯誤

類的準備

在準備階段,java虛擬機爲類的靜態變量分配內存,並設置默認的初始值,例如對於Sample類,在準備階段,將int類型的靜態變量a分配4個字節的內存
空間,而且賦予默認值0,爲long類型的靜態變量b分配8個字節的內存空間,而且賦予默認值0數據結構

類的解析

在解析階段,java虛擬機會把類的二進制數據中的符號引用替換爲直接引用,例如在Worker類的gotoWork()方法中會引用Car類的run()方法
public void gotoWorker(){ car.run(); }
在Worker類的二進制數據中,包含了一個對Car類的run()方法的符號引用,它由run()方法的全名和相關描述符組成,在解析階段,java虛擬機會把
這個符號引用替換爲一個指針,該指針指向Car類的run()方法在方法區內的內存位置,這個指針就是直接引用dom

類的初始化

在初始化階段,java虛擬機執行類的初始化語句,爲類的靜態變量賦予初始值,在程序中,靜態變量的初始化有兩種途徑:jvm

  • 在靜態變量的聲明處進行初始化
  • 在靜態代碼塊中進行初始化
    例如如下代碼中靜態變量c沒有被顯式初始化,它將保持默認值0
public class Sample{
    private static int a = 1; // 在靜態變量的聲明處進行初始化
    public static long b;
    public static long c;

    static{ b = 2; } // 在靜態代碼塊中進行初始化
}
class Singleton {
    //private static Singleton singleton = new Singleton();
    // counter1=1 counter2=0
    public static int counter1;
    public static int counter2 = 0;
    private static Singleton singleton = new Singleton(); 
    // counter1=1 counter2=1

    private Singleton(){counter1++; counter2++;}

    public static Singleton getInstance(){return singleton;}
}

public class MyTest{
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println("counter1= " + singleton.counter1);
        System.out.println("counter2= " + singleton.counter2);
    }
}

靜態變量的聲明語句,以及靜態代碼塊都被看作類的初始化語句,java虛擬機會按照初始化語句在類文件中的前後順序來依次執行它們
例如當如下Sample類被初始化後它的靜態變量a的取值爲4操作系統

public class Sample{
    static int a = 1;
    static{ a = 2; }
    static{ a = 4; }
    public static void main(String[] args){ System.out.println("a= " + a)} // 打印a=4
}

類的初始化步驟

  • 假如這個類尚未被加載和鏈接,那就先進行加載和鏈接
  • 假如類存在直接的父類,而且這個父類尚未被初始化,那就先初始化直接的父類
  • 假如類中存在初始化語句,那就依次執行這些初始化語句
class FinalTest{
    public static final int x = 6/3; 
    // 編譯時肯定 不會致使類初始化
    // public static final int x = new Random().nextInt(100);       // 運行時變量 對類進行初始化
    static{
        System.out.println("FinalTest static block");
    }
}

public class Test2 {
    public static void main(String[] args) {
        System.out.println(FinalTest.x);
    }
}

當java虛擬機初始化一個類時,要求它的全部父類都已經被初始化,可是這條規則並不適用於接口
在初始化一個類時,並不會先初始化它所實現的接口
在初始化一個接口時,並不會先初始化它的父接口
所以,一個父接口並不會由於它的子接口或者實現類的初始化而初始化,只有當程序首次使用特定接口的靜態變量時纔會致使接口初始化指針

public class Test3 {
    static{
        System.out.println("Test3 static block");
    }

    public static void main(String[] args) {
        System.out.println(Child.b);
    }
}

class Parent{
    static int a = 3;
    static{
        System.out.println("Parent static block");
    }
}

class Child extends Parent{
    static int b = 4;
    static{
        System.out.println("Child static block");
    }
}

輸出:
Test3 static block
Parent static block
Child static block
4

public class Test4 {
    static{
        System.out.println("Test4 static block");
    }

    public static void main(String[] args) {
        Parent parent;
        System.out.println(Parent2.a);
        System.out.println(Child2.b);
    }
}

class Parent2{
    static int a = 3;
    static{
        System.out.println("Parent2 static block");
    }
}

class Child2 extends Parent2{
    static int b = 4;
    static{
        System.out.println("Child2 static block");
    }
}

輸出:
Test4 static block
Parent2 static block
3
Child2 static block
4

對子類的主動使用會致使父類被初始化,但對父類的主動使用並不會致使子類初始化(不可能說生成一個Object類的對象致使全部子類初始化)

只有當程序訪問的靜態變量或靜態方法確實在當前類或當前接口中定義時,才能夠認爲是對類或接口的主動使用

public class Test5 {
    public static void main(String[] args) {
        System.out.println(Child3.a);
        Child3.doSomething();
    }
}

class Parent3{
    static int a = 3;
    static{
        System.out.println("Parent3 static block");
    }

    static void doSomething(){
        System.out.println("do something");
    }
}

class Child3 extends Parent3{
    static{
        System.out.println("Child3 static block");
    }
}

輸出:
Parent3 static block
3
do something

調用ClassLoader類的loadClass方法加載一個類,並非對類的主動使用,不會致使類的初始化

public class Test6 {
    public static void main(String[] args) throws Exception{
        ClassLoader loader = ClassLoader.getSystemClassLoader();
        loader.loadClass("com.ak.cls.C");
        System.out.println("line");
        Class.forName("com.ak.cls.C");
    }
}

class C{
    static{
        System.out.println("Class C");
    }
}

輸出: line Class C

相關文章
相關標籤/搜索