類的加載過程 以及實例的加載順序

類的加載過程java

(一)簡述類加載過程:數組

類加載過程: JVM虛擬機把.class文件中類信息加載進內存安全

.class文件: 經過javac命令將java文件編譯成字節碼 ,此時生成的字節碼文件稱爲.class文件網絡

類加載的通俗舉例: JVM在執行某段代碼時,遇到了class A,此時內存中並無class A的相關信息 ,JVM就會到相應的class文件中去尋找class A的類信息,並加載進內存中 。數據結構

注: JVM不是一開始就把全部的類都加載進內存中, 而是隻有第一次遇到某個須要運行的類時纔會加載,且只加載一次jvm

(二)Java虛擬機中類加載的全過程測試

參考:http://www.javashuo.com/article/p-bywnjqun-eq.htmlthis

https://blog.csdn.net/qq_31156277/article/details/80188110

1)加載.net

1.一、將.class字節碼經過類加載器加載到內存

            .class字節碼來源: 本地路徑下編譯生成的.class文件 ,從jar包中的.class文件 ,從遠程網絡,以及動態 

                                               代理實時編譯。       

             類加載器的分類:啓動類加載器、擴展類加載器 、應用類加載器 、用戶的自定義類加載器 。



	類加載的三個階段: 

            一、經過一個類的全限定名 (例:java/lang/Thread格式,使用 / 相隔)    獲取定義此類的二進制字節流

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

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

2)驗證代理

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

驗證的動做:

2.一、文件格式的驗證(基於二進制流進行)

	驗證字節流是否符合Class文件格式的規範 ,保證輸入的字節流能正確地解析並存儲於方法區以內 

	注:   驗證是基於二進制字節流進行的 ,只有經過了這個階段的驗證後,字節流才能進入內存中的方法                        

		  區進行存儲。



            例:常量中是否有不被支持的常量?文件中是否有不規範的或者附加的其餘信息? 



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

	對字節碼描述的信息進行語義分析 ,使其符合Java語言規範 

	例:該類是否繼承了被final修飾的類?類中的字段,方法是否與父類衝突?是否出現了不合理的重載? 



2.三、字節碼驗證(基於方法區的存儲結構 )

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

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



	例:

	 

2.四、符號引用驗證(基於方法區的存儲結構 )

	校驗符號引用中經過全限定名是否可以找到對應的類?校驗符號引用中的訪問性(private,public等) 		

	是否可被當前類訪問



注:驗證與加載交替執行,單是加載順序老是優於驗證執行

3)準備

爲類變量分配內存並設置類變量初始值 ,這些變量所使用的內存都將在方法區中進行分配

類變量:static修飾的變量

類變量初始值:public static int value=123; 在準備階段value的初始值爲0.而不是123

public static final int value=123;, 當被final修飾事物類變量 準備階段的初始值爲123

4)解析

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

符號引用:即一個字符串,可是這個字符串給出了一些可以惟一性識別一個方法,一個變量,一個類的相關信息

直接引用:能夠理解爲一個內存地址,或者一個偏移量

即把全部的類名,方法名,字段名這些符號引用替換爲具體的內存地址或偏移量,也就是直接引用

解析內容:

  1. 類或接口
  2. 字段
  3. 類方法
  4. 接口方法
  5. 方法類型
  6. 方法句柄
  7. 調用點限定符

驗證、準備、解析統稱爲鏈接

5)初始化

執行類構造器<clinit>()方法,即爲類變量賦值+執行靜態語句塊 static{}的過程

對類變量初始化 ,執行類構造器 ,只對static修飾的變量或語句進行初始化

初始化的順序:

(三)類的實例建立過程

參考:https://blog.csdn.net/qq_38537709/article/details/88750605

1.當建立子類實例時,先對父類的類變量(satic修飾的變量)和 static{}代碼塊進行初始化,再對子類的類變量(satic修飾的變量)和 static{}代碼塊進行初始化

2.對子父類完成初始化後調用構造器,子類的構造器第一行隱式的調用了父的空的構造器,在對父類構造器初始化

時先爲成員變量分配內存空間,再對構造器進行初始化

3.成員變量的賦值優先於構造方法裏的語句。

class  C{
    C() {
        System.out.println("正執行SuperClass類的構造方法對成員變量初始化,爲其成員變量C分配內存空間");
    }

}

class SuperClass {
    C c = new C();
    static{
        System.out.println("SuperClass類 正在初始化");
    }

    SuperClass() {
        this("正在調用SuperClass的有參構造方法");
        System.out.println("正在執行SuperClass的無參構造方法");
    }

    SuperClass(String s) {
        System.out.println(s);
    }
}

public class SubClass extends SuperClass{
    C c = new C();
    static{
        System.out.println("SubClass類 正在初始化");
    }
    SubClass() {
        /*在子類構造方法的第一句,隱式的調用父類的構造方法;*/
        System.out.println("正在執行子類SubClass的構造方法");
    }
    public static void main(String[] args) {
        new SubClass();
    }
}

執行結果:

SuperClass類 正在初始化
SubClass類 正在初始化
正執行SuperClass類的構造方法對成員變量初始化,爲其成員變量C分配內存空間
正在調用SuperClass的有參構造方法
正在執行SuperClass的無參構造方法
正執行SuperClass類的構造方法對成員變量初始化,爲其成員變量C分配內存空間
正在執行子類SubClass的構造方法

當子類調用父類的靜態字段時只有定義這個字段的類即父類才進行初始化,子類不初始化

父類

public class SuperClass {
    public static int value;

    public SuperClass() {
    }

    static {
        System.out.println("SuperClass Init");
        value = 123;
    }
}

子類

public class SubClass extends SuperClass {
    public SubClass() {
    }

    static {
        System.out.println("SubClass Init");
    }
}

測試:

public class Test {
    public Test() {
    }

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

結果:

SuperClass Init
123

使用類調用一個static final修飾的常量,該類不會進行初始化

public class ConstantClass {
    static{
        System.out.println("ConstantClass Init");
    }
    public static final String HELLOWOELD="hello world";
}

public class NotInitialization {
 
	public static void main(String[] args) {
		System.out.println(ConstantClass.HELLOWOELD);
	}
}

輸出結果:

hello world

當建立一個類類型的數組時,該類不會進行初始化

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

public class SubClass {

    public static void main(String[] args) {
        SupperClass[] supperClasses = new SupperClass[10];
    }
}

結果

無輸出結果:

總結:

一個類加載過程包含5個過程

一):加載, 首先將編譯過的字節碼文件經過類加載器加載入內存,將類轉爲二進制流的形式,將靜態時的存儲結構轉爲 方法區運行時的額數據結構,並生成java.lang.class對象

二):鏈接,鏈接的過程主要包括驗證、準備、解析三個步驟

驗證的過程是檢驗二進制流是否符合java規範,是否對jvm產生傷害,驗證分爲文件格式驗證(驗證二進制流是否符合.class文件規範),元數據驗證(對語義進行分析),字節碼驗證(對方法體進行校驗分析),符號引用驗檢驗是否能經過類名找到類對象,以及類的修飾符(private、public)

準備的過程是對類變量賦默認初值

解析的過程是將常量池內的符號引用替換爲直接引用的過程(將間接引用轉爲直接引用; 即將變量名稱轉爲對應的地址值或偏移量)

三):初始化,初始化是爲類變量賦值+執行靜態語句塊 static{}的過程

不會引起類初始化的3種狀況

1.子類引用父類的類變量,子類不會進行初始化,父類進行初始化

2.一個類引用本類的常量即 static fianl修飾的變量不會進行初始化

3.聲明一個類類型數組時也不會進行初始化

子類進行實例化的過程

1.父類先初始化,子類再初始化(先給父類的類變量賦初值+執行靜態塊static{},再執行子類的)

2.先執行父類的構造方法,在執行子類的構造方法

3.在執行構造方法前先對類的成員變量進行賦值

相關文章
相關標籤/搜索