類加載過程中幾個重點執行順序整理


 正文前先來一波福利推薦:

 福利一:

百萬年薪架構師視頻,該視頻可以學到很多東西,是本人花錢買的VIP課程,學習消化了一年,為了支持一下女朋友公眾號也方便大家學習,共享給大家。

福利二:

畢業答辯以及工作上各種答辯,平時積累了不少精品PPT,現在共享給大家,大大小小加起來有幾千套,總有適合你的一款,很多是網上是下載不到。

獲取方式:

微信關注 精品3分鍾 ,id為 jingpin3mins,關注后回復   百萬年薪架構師 ,精品收藏PPT  獲取雲盤鏈接,謝謝大家支持!

-----------------------正文開始---------------------------

 

類的加載過程:

1、 JVM會先去方法區中找有沒有相應類的.class存在。如果有,就直接使用;如果沒有,則把相關類的.class加載到方法區

2、 在.class加載到方法區時,會分為兩部分加載:先加載非靜態內容,再加載靜態內容

3、 加載非靜態內容:把.class中的所有非靜態內容加載到方法區下的非靜態區域內

4、 加載靜態內容:

    4.1、把.class中的所有靜態內容加載到方法區下的靜態區域內

    4.2、靜態內容加載完成之后,對所有的靜態變量進行默認初始化

    4.3、所有的靜態變量默認初始化完成之后,再進行顯式初始化

    4.4、當靜態區域下的所有靜態變量顯式初始化完后,執行靜態代碼塊

5,當靜態區域下的靜態代碼塊,執行完之后,整個類的加載就完成了。

6,如果存在繼承關系,則父類先加載,再加載子類。

實例的初始化過程:

1、在堆內存中開辟一塊空間

2、 給開辟空間分配一個地址

3、 把對象的所有非靜態成員加載到所開辟的空間下

4、 所有的非靜態成員加載完成之后,對所有非靜態成員變量進行默認初始化

5、 所有非靜態成員變量默認初始化完成之后,調用構造函數

6, 在構造函數入棧執行時,分為兩部分:先執行構造函數中的隱式三步再執行構造函數中書寫的代碼

    6.1、隱式三步:

        1,執行super語句

        2,對開辟空間下的所有非靜態成員變量進行顯式初始化

        3,執行構造代碼塊

    6.2、在隱式三步執行完之后,執行構造函數中書寫的代碼

7,在整個構造函數執行完並彈棧后,把空間分配的地址賦值給一個引用對象【注意是最后把地址值賦給棧中引用】

 

類的驗證過程:

驗證部分主要包含下面四個階段的驗證:

1. 文件格式的驗證:

     驗證文件格式是否按照虛擬機的規范,也就是我們前面class文件結構中的內容,比如這是不是一個Class文件(看魔數,是否位CAFEBABE);

    Java版本是否符合當前虛擬機的范圍(Java可以向下兼容,但是不能處理大於當前版本的程序)等等。

2. 元數據的驗證:

    對Class文件中的元數據進行驗證,是否存在不符合Java語義的元數據信息。這里有的朋友可能會比較疑惑,什么是元數據呢?一般情況下,一個文件中都數據和元數據。數據指的是實際數據,而元數據(Metadata)是用來描述       數據的數據。用過Java注解的朋友應該對元數據這種叫法並不陌生,對應的元注解,其實說的差不多都是一個意思。

   舉個例子:比如說我們定義了一個變量 int a = 1;可以理解成數據就是1,而元數據就是描述有一個字符串變量“a”,這個“a”的類型是int型的,它的值也是一個int型的1,這就是描述數據的數據,就是元數據。

3. 字節碼的驗證:

   通過數據流和控制流分析,來確定程序語義是否合法。

   以數據來說,要保證類型轉換是有效的;對於控制流程的代碼,不能讓指令跳轉到其它方法的字節碼指令上等……

4. 符號引用的驗證:

   為了保證解析動作能正常完成,還需在虛擬機將符號引用轉成直接引用的時候,判斷其它要引用的類是否符合規定。比如,要引用的類是否能夠被找到;引用的屬性在對應類中是否存在,權限是否符合要求(private的是不能訪問      的)等。

  最后再說一點,驗證階段不是必須的,如果代碼運行已經穩定了之后,可以通過設置參數-Xverfy:none參數來關閉類驗證,減少虛擬機的類加載時間,提高運行效率。

類的解析過程:

解析階段是將常量池中的符號引用替換為直接引用的過程。在進行解析之前需要對符號引用進行解析,不同虛擬機實現可以根據需要判斷到底是在類被加載器加載的時候對常量池的符號引用進行解析(也就是初始化之前),還是等到一個符號引用被使用之前進行解析(也就是在初始化之后)。

到現在我們已經明白解析階段的時機,那么還有一個問題是:如果一個符號引用進行多次解析請求,虛擬機中除了invokedynamic指令外,虛擬機可以對第一次解析的結果進行緩存(在運行時常量池中記錄引用,並把常量標識為一解析狀態),這樣就避免了一個符號引用的多次解析。

解析動作主要針對的是類或者接口、字段、類方法、方法類型、方法句柄和調用點限定符7類符號引用。這里主要說明前四種的解析過程。

類或者接口解析

要把一個類或者接口的符號引用解析為直接引用,需要以下三個步驟:

1. 如果該符號引用不是一個數組類型,那么虛擬機將會把該符號代表的全限定名稱傳遞給類加載器去加載這個類。這個過程由於涉及驗證過程所以可能會觸發其他相關類的加載

2. 如果該符號引用是一個數組類型,並且該數組的元素類型是對象。我們知道符號引用是存在方法區的常量池中的,該符號引用的描述符會類似”[java/lang/Integer”的形式,將會按照上面的規則進行加載數組元素類型,如果描述符如前面假設的形式,需要加載的元素類型就是java.lang.Integer ,接着由虛擬機將會生成一個代表此數組對象的直接引用

3. 如果上面的步驟都沒有出現異常,那么該符號引用已經在虛擬機中產生了一個直接引用,但是在解析完成之前需要對符號引用進行驗證,主要是確認當前調用這個符號引用的類是否具有訪問權限,如果沒有訪問權限將拋出java.lang.IllegalAccess異常

字段解析

對字段的解析需要首先對其所屬的類進行解析,因為字段是屬於類的,只有在正確解析得到其類的正確的直接引用才能繼續對字段的解析。對字段的解析主要包括以下幾個步驟:

1. 如果該字段符號引用就包含了簡單名稱和字段描述符都與目標相匹配的字段,則返回這個字段的直接引用,解析結束

2. 否則,如果在該符號的類實現了接口,將會按照繼承關系從下往上遞歸搜索各個接口和它的父接口,如果在接口中包含了簡單名稱和字段描述符都與目標相匹配的字段,那么久直接返回這個字段的直接引用,解析結束

3. 否則,如果該符號所在的類不是Object類的話,將會按照繼承關系從下往上遞歸搜索其父類,如果在父類中包含了簡單名稱和字段描述符都相匹配的字段,那么直接返回這個字段的直接引用,解析結束

4. 否則,解析失敗,拋出java.lang.NoSuchFieldError異常

如果最終返回了這個字段的直接引用,就進行權限驗證,如果發現不具備對字段的訪問權限,將拋出java.lang.IllegalAccessError異常

類方法解析

進行類方法的解析仍然需要先解析此類方法的類,在正確解析之后需要進行如下的步驟:

1. 類方法和接口方法的符號引用是分開的,所以如果在類方法表中發現class_index(類中方法的符號引用)的索引是一個接口,那么會拋出java.lang.IncompatibleClassChangeError的異常

2. 如果class_index的索引確實是一個類,那么在該類中查找是否有簡單名稱和描述符都與目標字段相匹配的方法,如果有的話就返回這個方法的直接引用,查找結束

3. 否則,在該類的父類中遞歸查找是否具有簡單名稱和描述符都與目標字段相匹配的字段,如果有,則直接返回這個字段的直接引用,查找結束

4. 否則,在這個類的接口以及它的父接口中遞歸查找,如果找到的話就說明這個方法是一個抽象類,查找結束,返回java.lang.AbstractMethodError異常

5. 否則,查找失敗,拋出java.lang.NoSuchMethodError異常

如果最終返回了直接引用,還需要對該符號引用進行權限驗證,如果沒有訪問權限,就拋出java.lang.IllegalAccessError異常

接口方法解析

同類方法解析一樣,也需要先解析出該方法的類或者接口的符號引用,如果解析成功,就進行下面的解析工作:

1. 如果在接口方法表中發現class_index的索引是一個類而不是一個接口,那么也會拋出java.lang.IncompatibleClassChangeError的異常

2. 否則,在該接口方法的所屬的接口中查找是否具有簡單名稱和描述符都與目標字段相匹配的方法,如果有的話就直接返回這個方法的直接引用。

3. 否則,在該接口以及其父接口中查找,直到Object類,如果找到則直接返回這個方法的直接引用

4. 否則,查找失敗

接口的所有方法都是public,所以不存在訪問權限問題

*************************************************************

類卸載過程分析:

類的生命周期

  當Sample類被加載、連接和初始化后,它的生命周期就開始了。

  當代表Sample類的Class對象不再被引用,即不可觸及時,Class對象就會結束生命周期,Sample類在方法區內的數據也會被卸載,從而結束Sample類的生命周期。

  由此可見,一個類何時結束生命周期,取決於代表它的Class對象何時結束生命周期。

引用關系

  加載器和Class對象:雙向引用

  在類加載器的內部實現中,用一個Java集合來存放所加載類的引用。

  另一方面,一個Class對象總是會引用它的類加載器。調用Class對象的getClassLoader()方法,就能獲得它的類加載器。

  由此可見,Class實例和加載它的加載器之間為雙向關聯關系。

  類、類的Class對象、類的實例對象:單向引用

  一個類的實例總是引用代表這個類的Class對象。

  在Object類中定義了getClass()方法,這個方法返回代表對象所屬類的Class對象的引用。

  此外,所有的Java類都有一個靜態屬性class,它引用代表這個類的Class對象。

類的卸載

  由Java虛擬機自帶的類加載器所加載的類,在虛擬機的生命周期中,始終不會被卸載。

  前面介紹過,Java虛擬機自帶的類加載器包括根類加載器、擴展類加載器和系統類加載器。

  Java虛擬機本身會始終引用這些類加載器,而這些類加載器則會始終引用它們所加載的類的Class對象,因此這些Class對象始終是可觸及的。

  由用戶自定義的類加載器加載的類是可以被卸載的。

具體例子

 

oader1變量和obj變量間接應用代表Sample類的Class對象,而objClass變量則直接引用它。

如果程序運行過程中,將上圖左側三個引用變量都置為null,此時Sample對象結束生命周期,MyClassLoader對象結束生命周期,代表Sample類的Class對象也結束生命周期,Sample類在方法區內的二進制數據被卸載。

當再次有需要時,會檢查Sample類的Class對象是否存在,如果存在會直接使用,不再重新加載;如果不存在Sample類會被重新加載,在Java虛擬機的堆區會生成一個新的代表Sample類的Class實例(可以通過哈希碼查看是否是同一個實例)。

【總結】

(1) 啟動類加載器加載的類型在整個運行期間是不可能被卸載的(jvm和jls規范);

(2) 被系統類加載器和標准擴展類加載器加載的類型在運行期間不太可能被卸載,因為系統類加載器實例或者標准擴展類的實例基本上在整個運行期間總能直接或者間接的訪問的到,其達到unreachable的可能性極小。(當然,在虛擬機快退出的時候可以,因為不管ClassLoader實例或者Class(java.lang.Class)實例也都是在堆中存在,同樣遵循垃圾收集的規則);

(3) 被開發者自定義的類加載器實例加載的類型只有在很簡單的上下文環境中才能被卸載,而且一般還要借助於強制調用虛擬機的垃圾收集功能才可以做到.可以預想,稍微復雜點的應用場景中(尤其很多時候,用戶在開發自定義類加載器實例的時候采用緩存的策略以提高系統性能),被加載的類型在運行期間也是幾乎不太可能被卸載的(至少卸載的時間是不確定的)

綜合以上三點, 一個已經加載的類型被卸載的幾率很小至少被卸載的時間是不確定的。同時我們可以看的出來,開發者在開發代碼時候,不應該對虛擬機的類型卸載做任何假設的前提下來實現系統中的特定功能。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM