虛擬機類加載機制

虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終造成能夠被虛擬機直接使用的Java類型,就是虛擬機的類加載機制。java

一. 類加載的時機數組

類的生命週期.png

    加載、驗證、準備、初始化和卸載這五個步驟的順序是肯定的。安全

    類的加載過程必須按這個順序開始,而解析階段在某些時候能夠在初始化以後開始,這是爲了支持Java語言的運行時綁定。數據結構

    什麼時候進行類加載過程的第一階段:加載並無進行強制約束,單對於初始化階段,虛擬機規範則是嚴格規定了有且只有5中狀況下必須當即對類進行「初始化」:jvm

        (1)遇到new、getstatic、putstatic或invokestatic這4條字節碼指令時,若是沒有類進行過初始化,則須要先出發其初始化;ide

        (2)使用java.lang.reflect包的方法對類進行反射調用的時候,若是類沒有進行過初始化,則須要先出發其初始化;函數

        (3)當初始化一個類的時候,若是發現其父類還沒進行過初始化,則需先觸發其父類的初始化;佈局

        (4)當虛擬機啓動時,用戶須要指定一個執行的主類(包含main()方法的那個類),虛擬機會先初始化這個主類;spa

        (5)當使用JDK1.7的動態語言支持時,若是一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,而且這個方法句柄所對應的類沒有進行過初始化,則須要先觸發其初始化。指針

        這5中場景中的行爲稱爲對一個類進行主動引用。

        除以上5中場景外全部引用類的方式都不會觸發初始化,被稱爲被動引用。

public class SuperClass {
/**
* 被動使用類字段演示:
* 經過子類引用父類的靜態字段,不會致使子類初始化
*/
static {
System.out.println("SuperClass init! ");
}
public static int value = 123;
}
public class SubClass extends SuperClass {
static {
System.out.println("SubClass init! ");
}
}
/**
* 被動使用類字段演示三:
* 常量在編譯階段會存入調用類的常量池中,本質上並無直接引用到定義常量的類,所以不會觸發定義常量的類的初始化
*/
public class ConstantClass {
static {
System.out.println("ConstantClass init! ");
}
public static final String HELLOWORLD = "hello world";
}
public class NotInitialization {
/**
* 非主動使用類字段演示
*/
public static void main(String[] args) {
//對於靜態字段,只有直接定義這個字段的類纔會被初始化,所以經過其子類來引用父類中的靜態字段,只會觸發父類的初始化而不會觸發子類的初始化
System.out.println(SubClass.value);
/**
* 被動使用類字段演示二:
*/
//經過數組定義來引用類,不會觸發此類的初始化
SuperClass[] sca = new SuperClass[10];
//非主動使用類字段演示
System.out.println(ConstantClass.HELLOWORLD);
}
}
運行結果:
SuperClass init!
123
hello world


    對於靜態字段,只有直接定義這個字段的類纔會被初始化,所以經過其子類來引用父類中的靜態字段,只會觸發父類的初始化而不會觸發子類的初始化

    經過數組定義來引用類,不會觸發此類的初始化;

    常量在編譯階段會存入調用類的常量池中,本質上並無直接引用到定義常量的類,所以不會觸發定義常量的類的初始化


二. 類加載的過程

    1.加載

        加載是」類加載「過程的一個階段。

        在加載階段,虛擬機須要完成3件事:

            -- 經過一個類的權限定名來獲取定義此類的二進制字節流;

            --將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構;

            --在內存中生成一個表明這個類的java.lang.Class對象,做爲方法區這個類的各類數據的訪問入口。


    2.驗證(類加載過程當中非必須的)

        目的:確保Class文件的字節流中包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。

        1)文件格式驗證(基於二進制字節流的驗證)

            保證輸入的字節流能正確地解析並存儲於方法區以內,格式上符合描述一個java類型信息的要求。

            經過該階段的驗證後,字節流纔會進入內存的方法區中進行存儲。因此後面三個驗證階段都是基於方法區的存儲結構進行的,不會再直接操做字節流。

        2)元數據驗證(基於方法區的存儲結構)

            對字節碼描述的信息進行語義分析,以保證其描述的信息符合Java語言規範的要求。即對元數據信息中的數據類型作檢驗。

        3)字節碼驗證

            經過數據流和控制流分析,肯定程序語義是合法的、符合邏輯的。

            對類的方法體進行校驗分析,保證被校驗類的方法在運行時不會作出危害虛擬機安全的事件。

        4)符號引用驗證(鏈接的第三階段--解析階段)

            發生在虛擬機將符號引用轉換爲直接引用的時候,這個轉化動做將在鏈接的第三階段--解析階段中發生。

            可看做是對類自身之外(常量池中的各類符號引用)的信息進行匹配校驗。


    3.準備

        正式爲類變量(static)分配內存並設置類變量初始值(零值)的階段。這些變量所使用的內存都將在方法區中進行分配。


    4.解析

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

        符號引用:以一組符號來描述所引用的目標。

                        符號引用與虛擬機實現的內存佈局無關,引用的目標並不必定已經加載到內存中。

        直接引用:能夠是直接指向目標的指針、相對偏移量或是一個能直接定位到目標的句柄。

                        直接引用時和虛擬機實現的內存佈局相關的。若是有了直接引用,那引用的目標一定已經在內存中存在。

        類或接口解析、字段解析、類方法解析、接口方法解析


    5.初始化

        初始化時類加載過程的最後一步。初始化階段才真正開始執行類中定義的Java程序代碼(字節碼)。

        初始化階段是執行類構造器<clinit>()方法的過程。

        <clinit>()方法:由編譯器自動收集類中的全部類變量的賦值動做和靜態語句塊(static{}塊)中的語句合併產生的。編譯器收集的順序是由語句在源文件中出現的順序決定的,靜態語句塊只能訪問到定義在靜態語句塊前的變量,定義在它以後的變量,在前面的靜態語句塊能夠賦值,但不能訪問。

        eg:非法向前引用

        image.png

        <clinit>()方法與類的構造函數(<init>())不一樣,它不須要顯式地調用父類構造器,虛擬機會保證在子類的<clinit>()方法執行以前,父類的<clinit>()方法已經執行完畢。


三. 類加載器

    經過一個類的全限定名來獲取描述此類的二進制字節流。實現這個動做的代碼塊稱爲類加載器。

    每一個類加載器都擁有一個獨立的類名稱空間。比較兩個類是否「相等(equals()、isAssignableFrom()、isInstance()、instanceof)」,只有在這兩個類是由同一個類加載器加載的前提下才有意義。

package jvm.classloader;
/**
* Created by turnsole on 2018/6/6.
*/
import java.io.IOException;
import java.io.InputStream;
/**
* 類加載器與instanceof關鍵字演示
*/
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
//自定義的類加載器加載
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is==null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};

Object obj = myLoader.loadClass("jvm.classloader.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof jvm.classloader.ClassLoaderTest);
}
}
/**
* 運行結果:
* class jvm.classloader.ClassLoaderTest
* false
*/





四. 虛擬機字節碼執行引擎

    1.運行時棧幀結構

        棧幀:用於支持虛擬機進行方法調用和方法執行的數據結構。是虛擬機運行時數據區中的虛擬機棧的棧元素。

        棧幀儲存了方法的局部變量表、操做數棧、動態鏈接和方法返回地址等信息。

相關文章
相關標籤/搜索