小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

JAVA-類加載(類的生命周期)

 印度阿三17 2019-05-22

類從被加載到虛擬機內存開始,到卸載出內存為止。

解析階段在某些情況下可以在初始化后再開始,這是為了支持 Java 語言的運行時綁定。

?

一、類加載時機

JVM 規(guī)范沒有強制約束類加載過程的第一階段(加載)什么時候開始,但對于“初始化”階段,有著嚴格的規(guī)定。

?

1.1.有且僅有 5 種情況必須立即對類進行“初始化”:

1.在遇到 new、putstatic、getstatic、invokestatic 字節(jié)碼指令時,如果類尚未初始化,則需要先觸發(fā)其初始化。
2.對類進行反射調用時,如果類還沒有初始化,則需要先觸發(fā)其初始化。
3.初始化一個類時,如果其父類還沒有初始化,則需要先初始化父類。
4.虛擬機啟動時,用于需要指定一個包含 main() 方法的主類,虛擬機會先初始化這個主類。
5.當使用 JDK 1.7 的動態(tài)語言支持時,如果一個 java.lang.invoke.MethodHandle 實例最后的解析結果為 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且這個方法句柄所對應的類還沒初始化,則需要先觸發(fā)其初始化。

這 5 種場景中的行為稱為對一個類進行主動引用,除此之外,其它所有引用類的方式都不會觸發(fā)初始化,稱為被動引用。

?

1.2.幾種被動引用:

1.通過子類引用父類的靜態(tài)字段,不會導致子類初始化。對于靜態(tài)字段,只有直接定義這個字段的類才會被初始化。

class SuperClass {
    static {
        System.out.println("SuperClass init!");
    }
    public static int value = 123;
}

class SubClass extends SuperClass {
    static {
        System.out.println("SubClass init!");
    }
}

public class NotInitialization {
    public static void main(String[] args) {
        System.out.println(SubClass.value);
        // SuperClass init!
    }
}
View Code

2.通過數(shù)組定義來引用類,不會觸發(fā)此類的初始化。

class SuperClass2 {
    static {
        System.out.println("SuperClass init!");
    }
    public static int value = 123;
}

public class NotInitialization2 {
    public static void main(String[] args) {
        SuperClass2[] superClasses = new SuperClass2[10];
    }
}
View Code

3.常量在編譯階段會存入調用類的常量池中,本質上并沒有直接引用到定義常量的類,因此不會觸發(fā)定義常量的類的初始化。

class ConstClass {
    static {
        System.out.println("ConstClass init!");
    }

    public static final String HELLO_BINGO = "Hello Bingo";
}

public class NotInitialization3 {
    public static void main(String[] args) {
        System.out.println(ConstClass.HELLO_BINGO);
    }
}
View Code

編譯通過之后,常量存儲到 NotInitialization 類的常量池中,NotInitialization 的 Class 文件中并沒有 ConstClass 類的符號引用入口,這兩個類在編譯成 Class 之后就沒有任何聯(lián)系了。

?

1.3.關于接口加載

當一個類在初始化時,要求其父類全部都已經(jīng)初始化過了,但是一個接口在初始化時,并不要求其父接口全部都完成了初始化,當真正用到父接口的時候才會初始化。

?

?

二、類的加載過程

?2.1.加載

JVM 需要完成 3 件事:

1.通過類的全限定名獲取該類的二進制字節(jié)流。
2.將二進制字節(jié)流所代表的靜態(tài)結構轉化為方法區(qū)的運行時數(shù)據(jù)結構。
3.在內存中創(chuàng)建一個代表該類的 java.lang.Class 對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口。

怎樣獲取類的二進制字節(jié)流,JVM 沒有限制。除了從編譯好的 .class 文件中讀取,還有以下幾種方式:

從 zip 包中讀取,如 jar、war 等
從網(wǎng)絡中獲取
通過動態(tài)代理生成代理類的二進制字節(jié)流
從數(shù)據(jù)庫中讀取
。。。

數(shù)組類本身不通過類加載器創(chuàng)建,由 JVM 直接創(chuàng)建,再由類加載器創(chuàng)建數(shù)組中的元素類。

加載階段與連接階段的部分內容交叉進行,但這兩個階段的開始仍然保持先后順序。

?

2.2.驗證

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

?

2.3.準備

為類變量(靜態(tài)成員變量)分配內存并設置初始值的階段。這些變量(不包括實例變量)所使用的內存都在方法區(qū)中進行分配。

基本類型初始值(JDK8)https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.12.5

對于 byte 類型,默認值為零,即(byte)0。
對于 short 類型,默認值為零,即(short)0。
對于 int 類型,默認值為零,即 0。
對于 long 類型,默認值為零,即 0L。
對于 float 類型,默認值為正零,即 0.0f。
對于 double 類型,默認值為正零,即 0.0d。
對于 char 類型,默認值為空字符,即 '\u0000'。
對于 boolean 類型,默認值為 false。
對于所有引用類型,默認值為 null。

存在特殊情況?https://www.jianshu.com/p/520295a63967

/**
 * 準備階段過后的初始值為 0 而不是 123,這時候尚未開始執(zhí)行任何 Java 方法
 */
public static int value = 123;

/**
 * 同時使用final 、static來修飾的變量(常量),并且這個變量的數(shù)據(jù)類型是基本類型或者String類型,就生成ConstantValue屬性來進行初始化。
 * 沒有final修飾或者并非基本類型及String類型,則選擇在<clinit>方法中進行初始化。
 * 準備階段虛擬機會根據(jù) ConstantValue 的設置將 value 賦值為 123
 */
public static final int value = 123;

?

2.4.解析

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

https://www.cnblogs.com/shinubi/articles/6116993.html

符號引用:一個 java 文件會編譯成一個class文件。在編譯時,java 類并不知道所引用的類的實際地址,因此只能使用符號引用來代替。

直接引用:直接指向目標的指針(指向方法區(qū),Class 對象)、指向相對偏移量(指向堆區(qū),Class 實例對象)或指向能間接定位到目標的句柄。

?

2.5.初始化

類加載過程的最后一步,是執(zhí)行類構造器 <clinit>() 方法的過程。

<init>()? 與 <clinit>() 介紹:??https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9

https://blog.csdn.net/u013309870/article/details/72975536

<init>():為 Class 類實例構造器,對非靜態(tài)變量解析初始化,一個類構造器對應個。

<clinit>():為 Class 類構造器對靜態(tài)變量,靜態(tài)代碼塊進行初始化,通常一個類對應一個,不帶參數(shù),且是 void 返回。當一個類沒有靜態(tài)語句塊,也沒有對類變量的賦值操作,那么編譯器可以不為這個類生成 <clinit>() 方法

加載順序:

<clinit>() 方法是由編譯器自動收集類中的所有類變量的賦值動作靜態(tài)語句塊(static {} 塊)中的語句合并產(chǎn)生的,編譯器收集的順序是由語句在源文件中出現(xiàn)的順序所決定的。

靜態(tài)語句塊中只能訪問定義在靜態(tài)語句塊之前的變量,定義在它之后的變量,在前面的靜態(tài)語句塊中可以賦值,但不能訪問。

static {
    i = 0;  // 給后面的變量賦值,可以正常編譯通過
    System.out.println(i);  // 使用后面的變量,編譯器會提示“非法向前引用”
}
static int i = 1;

虛擬機會保證在子類的 <clinit>() 方法執(zhí)行之前,父類的 <clinit>() 方法已經(jīng)執(zhí)行完畢。

由于父類的 <clinit>() 方法先執(zhí)行,意味著父類中定義的靜態(tài)語句塊要優(yōu)先于子類的變量賦值操作。

static class Parent {
    static {
        A = 2;
    }
    public static int A = 1;
}

static class Sub extends Parent {
    public static int B = A;
}

public static void main(String[] args) {
    System.out.println(Sub.B);  // 輸出 1
}

來看一個加載順序的問題

public static JvmTest jt = new JvmTest();

public static int a;
public static int b = 0;

static {
    a  ;
    b  ;
}

public JvmTest() {
    a  ;
    b  ;
}

public static void main(String[] args) {
    /**
     * 準備階段:為 jt、a、b 分配內存并賦初始值 jt=null、a=0、b=0
     * 解析階段:將 jt 指向內存中的地址
     * 初始化:jt 代碼位置在最前面,這時候 a=1、b=1
     *          a 沒有默認值,不執(zhí)行,a還是1,b 有默認值,b賦值為0
     *          靜態(tài)塊過后,a=2、b=1
     */
    System.out.println(a);  // 輸出 2
    System.out.println(b);  // 輸出 1
}

關于接口初始化:

接口中不能使用靜態(tài)代碼塊,但接口也需要通過 <clinit>() 方法為接口中定義的靜態(tài)成員變量顯式初始化。

接口與類不同,接口的 <clinit>() 方法不需要先執(zhí)行父類的 <clinit>() 方法,只有當父接口中定義的變量被使用時,父接口才會初始化。

?

虛擬機會保證一個類的 <clinit>() 方法在多線程環(huán)境中被正確加鎖、同步。如果多個線程同時去初始化一個類,那么只會有一個線程去執(zhí)行這個類的 <clinit>() 方法。


https://github.com/doocs/jvm/blob/master/docs/08-load-class-time.md

https://github.com/doocs/jvm/blob/master/docs/09-load-class-process.md

來源:http://www./content-1-202551.html

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多