日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

类加载机制--浅谈

發布時間:2025/3/15 编程问答 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 类加载机制--浅谈 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、定義:

? ? 類加載(Class Loading)是一種機制,他描述的是將字節碼以文件形式加載到內存再經過連接、初始化后,最終形成可以被虛擬機直接使用的Java類型地過程。

? ? Class Loading 包含了加載(Loading)、連接(Linking)、初始化(Initialization)三大部分,其中Linking又包含了三個部分:校驗(Verification)、準備? (Preparation)、解析(Resolution)。而一個類的生命周期只是在Class Loader的基礎上多了:使用(Using),卸載(Unloading)兩部分。

Class Loaders的組成:

二、加載階段

虛擬機需要完成以下3件事情:

1)通過一個類的全限定名來獲取定義此類的二進制字節流。

2)將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。

3)在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口。

注意:

? ? 1.JVM在加載數組的時候加載的僅僅是數組的類型類(例如String[] 加載器只會加載String這個類型類),而數組的創建則由JVM直接完成。

這里我們多問幾個為什么:
? ? 2. JVM為什么只加載數組的類型類:
我認為JVM這樣做的目的主要是為了節省時間,我們知道數組里面裝的都是同一種類型的元素,JVM沒必要將一個重復的內容加載多次浪費時間。
? ? 3. N維數組怎么加載:
如果是N維數組,類加載器會從最外層開始一層一層的遞歸加載,直到加載到非數組類型為止。
? ? 4. 引用類型與基本類型加載起來會不會有區別:
其實基本類型早已經在javac階段裝箱成封裝對象了,例如int會被裝箱成Integer,long裝箱成Long等等,所以是沒有區別的。

三、驗證

? ? ? 驗證是連接階段的第一步,這一階段目的是為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬機自身安全。

? ? ? 虛擬機規范對這個階段的限制和指導非常籠統,僅僅說了一句如果驗證到輸入的字節流不符合Class文件的存儲格式,就拋出一個java.lang.VerifyError異常或者其子類異常。具體應當檢查哪些方面,如何檢查,何時檢查,都沒有強制要求或明確說明,所以不同的虛擬機對驗證的實現可能會有所不同,但大致上都會完場下面四個階段的檢驗過程:文件格式驗證、元數據驗證、字節碼驗證和符號引用驗證。

? ? ? ? 是連接階段的第一步,這一階段的目的是為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬機自身的安全。但從整體上看,驗證階段大致上會完成下面4個階段的檢驗動作:文件格式驗證、元數據驗證、字節碼驗證、符號引用驗證。

  • 文件格式驗證:驗證字節流是否符合Class文件格式的規范;例如:是否以魔術0xCAFEBABE開頭、主次版本號是否在當前虛擬機的處理范圍之內、常量池中的常量是否有不被支持的類型。
  • 元數據驗證:對字節碼描述的信息進行語義分析(注意:對比javac編譯階段的語義分析),以保證其描述的信息符合Java語言規范的要求;例如:這個類是否有父類,除了java.lang.Object之外。
  • 字節碼驗證:通過數據流和控制流分析,確定程序語義是合法的、符合邏輯的。
  • 符號引用驗證:確保解析動作能正確執行。
  • 四、準備階段

    ? ? ? ?是正式為類變量分配內存并設置類變量初始值的階段,這些變量所使用的內存都將在方法區中進行分配。

    ? ? ? ?這個階段中有兩個容易產生混淆的概念需要強調一下,首先,這時候進行內存分配的僅包括類變量(被static修飾的變量),而不包括實例變量,實例變量將會在對象實例化時隨著對象一起分配在Java堆中。其次,這里所說的初始值“通常情況”下是數據類型的零值,假設一個類變量的定義為:

    public static int value=123;

    那變量value在準備階段過后的初始值為0而不是123,因為這時候尚未開始執行任何Java方法,而把value賦值為123的putstatic指令是程序被編譯后,存放于類構造器<clinit>()方法之中,所以把value賦值為123的動作將在初始化階段才會執行。表7-1列出了Java中所有基本數據類型的零值。

    假設上面類變量value的定義變為:public static final int value=123;

    編譯時Javac將會為value生成ConstantValue屬性,在準備階段虛擬機就會根據ConstantValue的設置將value賦值為123。

    五、解析階段

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

    六、初始化

    在連接的準備階段,類變量已賦過一次系統要求的初始值,而在初始化階段,則是根據程序員自己寫的邏輯去初始化類變量和其他資源,舉個例子如下:

    public static int value1 = 5;public static int value2 = 6;static{value2 = 66;}

    在準備階段value1和value2都等于0;

    在初始化階段value1和value2分別等于5和66;

    • 所有類變量(類變量是聲明在class內,method之外,且使用static修飾的變量)初始化語句和靜態代碼塊都會在編譯時被前端編譯器放在收集器里頭,存放到一個特殊的方法中,這個方法就是<clinit>方法,即類/接口初始化方法,該方法只能在類加載的過程中由JVM調用;
    • 編譯器收集的順序是由語句在源文件中出現的順序所決定的,靜態語句塊中只能訪問到定義在靜態語句塊之前的變量;
    • 如果超類還沒有被初始化,那么優先對超類初始化,但在<clinit>方法內部不會顯示調用超類的<clinit>方法,由JVM負責保證一個類的<clinit>方法執行之前,它的超類<clinit>方法已經被執行。
    • JVM必須確保一個類在初始化的過程中,如果是多線程需要同時初始化它,僅僅只能允許其中一個線程對其執行初始化操作,其余線程必須等待,只有在活動線程執行完對類的初始化操作之后,才會通知正在等待的其他線程。(所以可以利用靜態內部類實現線程安全的單例模式)
    • 如果一個類沒有聲明任何的類變量,也沒有靜態代碼塊,那么可以沒有類<clinit>方法;

    何時觸發初始化

  • 為一個類型創建一個新的對象實例時(比如new、反射、序列化)
  • 調用一個類型的靜態方法時(即在字節碼中執行invokestatic指令)
  • 調用一個類型或接口的靜態字段,或者對這些靜態字段執行賦值操作時(即在字節碼中,執行getstatic或者putstatic指令),不過用final修飾的靜態字段除外,它被初始化為一個編譯時常量表達式
  • 調用JavaAPI中的反射方法時(比如調用java.lang.Class中的方法,或者java.lang.reflect包中其他類的方法)
  • 初始化一個類的派生類時(Java虛擬機規范明確要求初始化一個類時,它的超類必須提前完成初始化操作,接口例外)
  • JVM啟動包含main方法的啟動類時。
  • 七、系統的類加載器

    類加載器名稱

    加載的范圍

    啟動類加載器

    Bootstrap ClassLoader

    存放在<JAVA_HOME>\lib目錄中的,并且是虛擬機 識別的類庫加載到虛擬機內存中

    擴展類加載器 Extension ClassLoader

    存放在<JAVA_HOME>\lib\ext目錄中的所有類庫, 開發者可以直接使用;

    應用程序加載器 Application ClassLoader

    加載用戶類路徑上指定的類庫,開發者可以直 接使用,一般情況下這個就是程序中默認的類 加載器;

    八、雙親委派機制

    ? ? ? ? 某個特定的類加載器在接到加載類的請求 時,首先將加載任務委托給父類加載器, 依次遞歸,如果父類加載器可以完成類加 載任務,就成功返回;只有父類加載器無 法完成此加載任務時,才自己去加載

    ? ? ? ? 雙親委派模型好處:Java類隨著它的類加載器一起具備了帶 有優先級的層次關系,保證java程序穩 定運行

    九、示例代碼

    public class SuperClazz {public SuperClazz(){System.out.println("我是父類構造方法");}static {System.out.println("我是父類靜態代碼塊");}public static int value = 123;public static final String STR = "hello world";public static final int WHAT = value; }public class SonClazz extends SuperClazz {public SonClazz(){System.out.println("我是子類構造方法");}static {System.out.println("我是子類靜態代碼塊");} }
    public class DemoTest {
    public static void main(String args[]){ /* System.out.println(SonClazz.value);
    輸出結果:
    我是父類靜態代碼塊
    123
    結論:對于靜態字段,只有直接定義這個字段的類,才能被初始化。
    */
    /*
    SuperClazz[] aa = new SuperClazz[10];
    輸出結果:(什么也沒有輸出)
    結論:只是聲明了一個數組形式的變量,類沒有初始化
    */

    /*
    System.out.println(SonClazz.STR);
    輸出結果:hello world
    結論:編譯期的傳播優化,因為是常量,所以就會直接編譯到了運行的類里面,就不會再去定義的類里面找了
    ,STR還是在常量池里面的;
    */

         /**

          我是父類靜態代碼塊
          123

        */
    //System.out.println(SonClazz.WHAT);


    } ? public class DemoTest2 {static{i=0;System.out.println(i);//這句編譯器會報錯:Cannot reference a field before it is defined(非法向前應用) }static int i=1; } public class DemoTest2 {static{i=2; // System.out.println(i); }static int i=1;public static void main(String args[]){System.out.println(i);} }輸出結果:1

    ?

    轉載于:https://www.cnblogs.com/lys-lyy/p/10771751.html

    總結

    以上是生活随笔為你收集整理的类加载机制--浅谈的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。