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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

JVM 类加载机制

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

jvm將描述java類的.class的字節碼文件加載到內存中,并對文件中的數據進行安全性校驗、解析和初始化,最終形成可以被java虛擬機直接使用的java類型,這個復雜的過程為jvm的類加載機制

類從被java到虛擬機內存開始,直到被卸載出內存為止,整個生命周期如上圖所示,分為:加載、驗證、準備、解析、初始化、使用、卸載共7個階段。

class類的初始化時機

虛擬機規范嚴格規定,有且只有5種情況,必須立即對類進行初始化:

  • 1、遇到new(對應新建對象)、getstatic(對應訪問類變量、static final除外)、putstatic(對應設置類變量、static final除外)、invokestatic(調用類方法即靜態方法)這4條指令時,如果類沒有初始化,則需要先觸發其初始化。
  • 2、對類進行反射調用時,如果類沒有初始化,則必須觸發初始化
  • 3、當初始化一個類時,如果其父類沒有初始化,則必須先初始化他的父類
  • 4、虛擬機執行的main方法入口,這個類必須初始化
  • 5、JDK1.7.動態語言支持時,如果java.lang.invoke.MethodHandler指向的是REF_getstatic,REF_putstatic、REF_invokestatic的方法句柄時,必須先初始化這個類

這兩種情況不會進行類的初始化

  • 1、申明數組類:Cat [] cat = new Cat[10];
  • 2、如下,B類不會初始化,A類會初始化
public class Test {public static void main(String[] args) {System.out.println(B.value); } }class A {public static int value = 123;}class B extends A {}

類加載的過程

加載

在加載階段,虛擬機需要完成以下3件事情:

  • 1、通過一個類的全限定名來獲取定義此類的二進制字節流。對于字節流的獲取,jvm并沒有明確的限制

    • 從ZIP包中讀取
    • 從網絡中獲取
    • 運行時計算生成
    • 從其他文件生成,如JSP技術
  • 2、將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構
  • 3、在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口

連接

連接是很重要的一步,過程比較復雜,分為三步 驗證 》準備 》解析   

  • 驗證:確保類加載的正確性。一般情況由javac編譯的class文件是不會有問題的,但是可能有人的class文件是自己通過其他方式編譯出來的,這就很有可能不符合jvm的編 譯規則,這一步就是要過濾掉這部分不合法文件 
  • 準備:為類的靜態變量分配內存,將其初始化為默認值 。我們都知道靜態變量是可以不用我們手動賦值的,它自然會有一個初始值 比如int 類型的初始值就是0 ;boolean類型初始值為false,引用類型的初始值為null 。 這里注意,只是為靜態變量分配內存,此時是沒有對象實例的 
  • 解析:把類中的符號引用轉化為直接引用。解釋一下符號引用和直接引用。比如在方法A中使用方法B,A(){B();},這里的B()就是符號引用,初學java時我們都是知道這是java的引用,以為B指向B方法的內存地址,但是這是不完整的,這里的B只是一個符號引用,它對于方法的調用沒有太多的實際意義,可以這么認為,他就是給程序員看的一個標志,讓程序員知道,這個方法可以這么調用,但是B方法實際調用時是通過一個指針指向B方法的內存地址,這個指針才是真正負責方法調用,他就是直接引用。

初始化

  • 初始化階段是執行類構造器()方法的過程。類構造器()方法是由編譯器自動收藏類中的所有類變量的賦值動作和靜態語句塊(static塊)中的語句合并產生
  • 當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化
  • 虛擬機會保證一個類的()方法在多線程環境中被正確加鎖和同步
  • 當范圍一個Java類的靜態域時,只有真正聲名這個域的類才會被初始化
  • 類的主動引用(一定會發生類的初始化)

    • new一個類的對象
    • 調用類的靜態成員(除了final常量)和靜態方法
    • 使用java.lang.reflect包的方法對類進行反射調用
    • 當虛擬機啟動,java Demo01,則一定會初始化Demo01類,說白了就是先啟動main方法所在的類
    • 當初始化一個類,如果其父類沒有被初始化,則先初始化它父類
  • 類的被動引用(不會發生類的初始化)

    • 當訪問一個靜態域時,只有真正聲名這個域的類才會被初始化

      • 通過子類引用父類的靜態變量,不會導致子類初始化
    • 通過數組定義類的引用,不會觸發此類初始化
    • 引用常量不會觸發此類的初始化(常量在編譯階段就存入調用類的常量池中了)

jvm類加載器

虛擬機設計團隊把加載動作放到JVM外部實現,以便讓應用程序決定如何獲取所需的類,實現這個動作的代碼模塊稱為“類加載器”。JVM提供了3種類加載器:

  • 啟動類加載器(Bootstrap ClassLoader):負責加載 JAVA_HOMElib 目錄中的,或通過-Xbootclasspath參數指定路徑中的,且被虛擬機認可(按文件名識別,如rt.jar)的類。
  • 擴展類加載器(Extension ClassLoader):負責加載 JAVA_HOMElibext 目錄中的,或通過java.ext.dirs系統變量指定路徑中的類庫。
  • 應用程序類加載器(Application ClassLoader):負責加載用戶路徑(classpath)上的類庫。
  • 用戶自定義類加載器(Customized Class Loader):用戶可以自己定義類加載器來加載類。所有的類加載器都要繼承java.lang.ClassLoader類。

JVM通過雙親委派模型進行類的加載,當然我們也可以通過繼承java.lang.ClassLoader實現自定義的類加載器。

類加載器加載的class在堆內存中的模型:

認識雙親委派模型

對于某個特定的類加載器而言,應該為其指定一個父類加載器,當用其進行加載類的時候:

  • 委托父類加載器幫忙加載;
  • 父類加載器加載不了,則查詢引導類加載器有沒有加載過該類;
  • 如果引導類加載器沒有加載過該類,則當前的類加載器應該自己加載該類;
  • 若加載成功,返回 對應的Class 對象;若失敗,拋出異常“ClassNotFoundException”。

請注意:
雙親委派模型中的"雙親"并不是指它有兩個父類加載器的意思,一個類加載器只應該有一個父加載器。上面的步驟中,有兩個角色:

  • 父類加載器(parent classloader):它可以替子加載器嘗試加載類
  • 引導類加載器(bootstrap classloader): 子類加載器只能判斷某個類是否被引導類加載器加載過,而不能委托它加載某個類;換句話說,就是子類加載器不能接觸到引導類加載器,引導類加載器對其他類加載器而言是透明的。

雙親模型如下圖:

AppClassLoader的工作流程如下圖:

雙親委派模型底層代碼實現:

//提供class類的二進制名稱表示,加載對應class,加載成功,則返回表示該類對應的Class<T> instance 實例public Class<?> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);}protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// 首先,檢查是否已經被當前的類加載器記載過了,如果已經被加載,直接返回對應的Class<T>實例Class<?> c = findLoadedClass(name);//初次加載if (c == null) {long t0 = System.nanoTime();try {if (parent != null) {//如果有父類加載器,則先讓父類加載器加載c = parent.loadClass(name, false);} else {// 沒有父加載器,則查看是否已經被引導類加載器加載,有則直接返回c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}// 父加載器加載失敗,并且沒有被引導類加載器加載,則嘗試該類加載器自己嘗試加載if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();// 自己嘗試加載c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}//是否解析類 if (resolve) {resolveClass(c);}return c;}}

如何破壞雙親委托模型:

  • 1、在構造類加載器時將parent設為null(構造函數參數為null即可,可以看下源碼)
  • 2、重寫loadloadClass(String,boolean)方法,改變類的查找機制

總結

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

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