7.類加載機制

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

Java動態擴展的特性依賴運行期動態加載和動態鏈接這個特性數據庫

 

類加載過程api

  加載數組

  鏈接:驗證、準備、解析(某些狀況能夠初始化以後開始,是爲支持Java運行時綁定特性,動態綁定)安全

  初始化服務器

  使用網絡

  卸載數據結構

 

類加載的時機多線程

  主動引用模塊化

    1.遇到new、getstatic、putstatic或invokestatic這四條字節碼指令時

      使用new關鍵字實例化對象的時候

      讀取或設置一個類的靜態字段(被final修飾已在編譯期把結果放入常量池的除外)    

      調用一個類型的靜態方法時

    2.使用Java.lang.reflect包的方法對類型進行反射調用的時候,若是類型沒有初始化,則先進行其初始化

    3.初始化的類,其父類尚未初始化,則先觸發其父類初始化

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

    5.當使用JDK7新加入的動態語言支持時,若java.lang.invoke.MethodHandle實例最後的解析結果爲REF_getStatic、REF_putStatic、REF_newInbokeSpecial四種類型的方法句柄,而且這個方法句柄對應的類沒有初始化,則須要先觸發其初始化

    6.當一個接口中定義了JDK8新加入的默認方法(被default關鍵字修飾的接口方法),若是有這個類的實現類發生了初始化,那麼該接口要在其以前初始化

  被動引用

    1.經過子類引用父類的靜態字段,不會致使子類初始化

    2.經過數組定義來引用類,不會觸發此類的初始化(例如:Student[]  ss=new Student[10];)字節碼指令newarray

    3.常量在編譯階段會存入調用類的常量池中,本質上沒有直接引用到定義常量的類,所以不會觸發該類的初始化 (static  final XXX)

 

類加載的過程

  加載、驗證、準備、解析

  加載

    需完成三個事情(數組類有所不一樣)

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

          從zip包中讀取(JAR、EAR、WAR)、

          網絡中獲取(最典型的應用Web Applet)、

          運行時計算生成(動態代理:java。lang。reflect。Proxy中用ProxyGenerator。generateProClass()來爲特定接口生成形式爲*$Proxy的代理類的二進制字節流)、

          其餘文件生成(JSP應用,JSP生成Class文件)、 

          從數據庫中讀取(中間件服務器SAPNetweaver),

          從加密文件中獲取(防Class文件反編譯)

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

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

  驗證

      文件格式驗證:驗證字節流是否符合Class文件格式的規範,並能被當前版本的虛擬機處理

          是否以魔數開頭

          主次版本號是否在Java虛擬機的接受範圍以內

          常量池中是否有不被支持的常量類型(檢查常量tag標誌)

          等等,主要的目的是保證輸入的字節流可以正確的解析並存儲於方法區以內,格式上符合一個java類型信息的要求。只有經過這個階段的驗證,虛擬機纔會讓字節流進入到方法區中進行存儲,後面的驗證都是直接操做在方法區之上,而不是直接操做字節流。

      元數據驗證:對字節碼描述的信息進行語義分析,以保證其描述符合java語言規範的要求。

          這個類是否有父類,java中除了Object,其它的類必須存在父類,默認爲Object。

          這個類是否extends了不被容許的類,如被final修飾的類。

          若是這個類不是抽象類,是否實現了其父類或接口之中須要實現的全部方法。

          類中的字段、方法是否與父類產生矛盾,如覆蓋了父類的final字段和方法,覆蓋不符合規則等等。  

      字節碼驗證:主要目的是經過數據流和控制流分析,肯定程序語義是合法的、符合邏輯的。主要對方法體(Code屬性)進行驗證,防止方法在運行時,不會作出對虛擬機有危害的操做。

          保證任意時刻操做數棧的數據類型與指令碼序列都可以配合工做,不會出現相似,在操做棧放置了一個int類型的數據,使用時卻按long類型來載入本地變量表中。(基於棧的指令集和基於寄存器的指令集8.5.2
          保證跳轉指令不會跳到方法體之外的字節碼指令上。
          保證類型轉換是有效的,例如把父類型賦值給子類型是安全的,子類型賦值給父類型就是不安全危險的。

      符號引用驗證:在解析階段發生,能夠看做是對類自身之外(常量池中的各類符號引用)的各種信息進行匹配性校驗,確保解析行爲正常執行

          符號引用中經過字符串描述的全限定名是否能找到對應的類

          在指定類中是否存在方法的字段描述符以及簡單名稱所描述的方法和字段

          符號引用中的類、字段、方法的可訪問性(private、protected、public、<package>)是否可被當前類訪問

          等等

  準備:爲類中定義的變量(靜態變量,被static修飾)分配內存並設置類變量的初始值,這些內存應當在方法區中分配

      JDK7以前:HotSpot使用永久代(年輕代、老年代、永久代)實現方法區

      JDKK8及以後:類變量隨Class對象一塊兒存放在Java堆中

      public staic int value=123;

      準備階段以後初始值爲0,因這時未執行任何方法,把value賦值爲123的putstatic指令是被程序編譯後存放於類構造器<clinit>()方法中,因此把value賦值爲123要到類的初始化階段還會被執行

      public staic final int value=123;(字段表屬性ConstantValue) 

      編譯時javac會爲value生成ConstantValue屬性,準備階段根據ConstantValue的設置將value賦值爲123

  解析:是虛擬機將常量池內的符號引用替換爲直接飲用的過程。http://www.javashuo.com/article/p-noalrwka-km.html

      符號引用(與虛擬機無關,不一樣的虛擬機翻譯出來必然相同):符號引用以一組符號來描述所引用的目標,符號能夠是任何形式的字面量,只要可以無歧義的定位到目標便可。符號引用的字面量形式明肯定義在java虛擬機規範的Class文件中。

      直接引用(與虛擬機有關,不一樣的虛擬機翻譯出來通常不會相同):直接引用能夠是直接指向目標的指針、相對偏移量或是一個可以間接定位到目標的句柄。
      1.類或接口的解析

          若是C不是一個數組類型,那虛擬機將會把表明N的全限定名傳遞給D的類加載器去加載這個類C。在加載過程當中,因爲元數據驗證、字節碼驗證的須要,又可能觸發其餘相關類的加載動做,例如加載這個類的父類或實現的接口。一旦這個加載過程出現了任何異常,解析過程就宣告失敗。

          若是C是一個數組類型,而且數組的元素類型爲對象,也就是N的描述符會是相似「[Ljava/lang/Integer」的形式,那將會按照第1點的規則加載數組元素類型。若是N的描述符如前面所假設的形式,須要加載的元素類型就是「java.lang.Integer」,接着由虛擬機生成一個表明此數組維度和元素的數組對象。

          若是上面的步驟沒有出現任何異常,那麼C在虛擬機中實際上已經成爲一個有效的類或接口了,但在解析完成以前還要進行符號引用驗證,確認D是否具有對C的訪問權限。若是發現不具有訪問權限,將拋出java.lang.IllegalAccessError異常。

      2.字段解析(要解析一個未被解析過的字段符號引用,首先將會對字段表內class_index項中索引的CONSTANT_Class_info符號引用進行解析,也就是字段所屬的類或接口的符號引用。若是在解析這個類或接口符號引用的過程當中出現了任何異常,都會致使字段符號引用解析的失敗。)

             若類自己包含該字段了簡單名稱和字段描述都和目標相符合的字段,直接返回此字段的直接引用,查找結束。

             若類自己找不到,則按照繼承關係找父接口,則返回字段引用。

          若類自己(該類不是java.lang.Object)找不到,則按照繼承關係找父接口,則返回字段引用。

          如果一直沒有找到則拋出java.lang.NoSuchFieldError異常。若成功返回了引用,將會對這個字段進行權限驗證,若不具有對字段的訪問權限,拋出java.lang.IllegalAccessError異常

          在實際應用中,虛擬機的編譯器實現可能會比上述規範要求的更加嚴格一些,若是有一個同名字段同時出如今C的接口和父類中,或者同時在本身或父類的多個接口中出現,那編譯器將可能拒絕編譯。

      3.方法解析

          類方法解析的第一個步驟與字段解析同樣,須要先解析出類方法表的class_index項中索引的方法所屬的類或接口的符號引用,若是解析成功,進行如下

          類方法和接口方法符號引用的常量類型定義是分開的,若是在類方法表中發現class_index中索引的C是個接口,那就直接拋出java.lang.IncompatibleClassChangeError異常。
          若是經過了第1步,在類C中查找是否有簡單名稱和描述符都與目標相匹配的方法,若是有則返回這個方法的直接引用,查找結束。
          不然,在類C的父類中遞歸查找,若是有則返回這個方法的直接引用,查找結束。
          不然,在類C實現的接口列表及他們的父接口之中遞歸查找,若是存在匹配的方法,說明類C是一個抽象,這時查找結束,拋出java.lang.AbstractMethodError異常。
          不然,宣告方法查找失敗,拋出java.lang.NoSuchMethodError。
          最後,若是查找過程成功返回了直接引用,將會對這個方法進行權限驗證,若是發現不具有對此方法的訪問權限,將拋出java.lang.IllegalAccessError異常。

      4.接口方法解析

          接口方法須要先解析出接口方法表的class_index項中索引的方法所屬的類或接口的符號引用,若是解析成功,進行如下

          與類方法解析不一樣,若是在接口方法表中發現class_index中的索引C是個類而不是接口,那就直接拋出java.lang.IncompatibleClassChangeError異常。
          不然,在接口C中查找是否有簡單名稱和描述符都與目標相匹配的方法,若是有則返回這個方法的直接引用,查找結束。
          不然,在接口C的父接口中遞歸查找,直到java.lang.Object(查找範圍會包括Object類)爲止,若是有則返回這個方法的直接引用,查找結束。
          不然,宣告方法查找失敗,拋出java.lang.NoSuchMethodError異常。
          因爲接口中的全部方法默認都是public,因此不存在訪問權限的問題,所以接口方法的符號解析應當不會拋出java.lang.IllegalAccessError異常。

 

  初始化:執行類構造器<clinit>()的過程

      <clinit>()是由編譯器自動收集類中全部類變量的賦值動做和靜態語句塊合併產生的

          編譯器收集的順序是由語句在源文件中出現的順序決定的,靜態語句塊中只能訪問到定義在靜態語句塊以前的變量,而定義在它以後的變量,在前面的靜態語句塊能夠賦值,但不能訪問    

          初始化方法執行的順序,虛擬機會保證在子類的初始化方法執行以前,父類的初始化方法已經執行完畢,所以在虛擬機中第一個被執行的類初始化方法必定是java.lang.Object。另外,也意味着父類中定義的靜態語句塊要優先於子類的變量賦值操做

          clinit ()方法對於類或接口來講並非必須的,若是一個類中沒有靜態語句塊,也沒有對變量的賦值操做,那麼編譯器能夠不爲這個類生成clinit()方法。

          接口中不能使用靜態語句塊,但仍然有變量初始化的操做,所以接口與類同樣都會生成clinit()方法,但與類不一樣的是,執行接口的初始化方法以前,不須要先執行父接口的初始化方法。只有當父接口中定義的變量使用時,纔會執行父接口的初始化方法。另外,接口的實現類在初始化時也同樣不會執行接口的clinit()方法。

          虛擬機會保證一個類的clinit()方法在多線程環境中被正確的加鎖、同步,若是多個線程同時去初始化一個類,那麼只會有一個線程去執行這個類的clinit()方法,其餘線程都須要阻塞等待,直到活動線程執行類初始化方法完畢

類加載器

    任意一個類:加載它的類加載器 + 類自己  ===》確立在虛擬機中的惟一性

    雙親委派模型

      類加載器:啓動類加載器(Bootstrap ClassLoader)、全部其餘類加載器(所有繼承抽象類java.lang.ClassLoader)

      三層類加載器

          啓動類加載器(Bootstrap ClassLoader):負責加載<JAVA_HOME>\bin目錄,例如:rt.jar,tools.jar

          擴展類加載器(Extension ClassLoader):負責加載<JAVA_HOME>\bin\ext目錄,或被java。ext。dirs系統變量指定路徑的類庫

          應用程序類加載器(Appliaction ClassLoader):用戶路徑上(ClassPath)全部的類庫

          

 

       工做過程:

          若是一個類加載器收到了類加載請求,它並不會本身先去加載,而是把這個請求委託給父類的加載器去執行,若是父類加載器還存在其父類加載器,則進一步向上委託,依次遞歸,請求最終將到達頂層的啓動類加載器,若是父類加載器能夠完成類加載任務,就成功返回,假若父類加載器沒法完成此加載任務,子加載器纔會嘗試本身去加載

 

      優勢:

          Java類隨着它的類加載器一塊兒具有了一種帶有優先級的層次關係,經過這種層級關能夠避免類的重複加載,當父親已經加載了該類時,就沒有必要子ClassLoader再加載一次。

          考慮到安全因素,java核心api中定義類型不會被隨意替換,假設經過網絡傳遞一個名爲java.lang.Integer的類,經過雙親委託模式傳遞到啓動類加載器,而啓動類加載器在覈心Java API發現這個名字的類,發現該類已被加載,並不會從新加載網絡傳遞的過來的java.lang.Integer,而直接返回已加載過的Integer.class,這樣即可以防止核心API庫被隨意篡改

      問題:    

          若是咱們在classpath路徑下自定義一個名爲java.lang.SingleInterge類(該類是胡編的)呢?該類並不存在java.lang中,通過雙親委託模式,傳遞到啓動類加載器中,因爲父類加載器路徑下並無該類,因此不會加載,將反向委託給子類加載器加載,最終會經過系統類加載器加載該類。可是這樣作是不容許,由於java.lang是核心API包,須要訪問權限,強制加載將會報出以下異常java.lang.SecurityException: Prohibited package name: java.lang



    破壞雙親委派模型

      OSGI熱部署

          過程

          優缺點

7.5Java模塊化系統(待閱)        

相關文章
相關標籤/搜索