天天看點

類加載

類加載

 一.歸納

類加載

java中所有類加載的過程都是按照加載、驗證、準備、初始化、解除安裝這幾個步驟開始的 , 而解析則不一定, 當遇到動态綁定或者晚期綁定的情況下 , 可以在初始化之後再開始  .

虛拟機把描述類的資訊從class檔案加載到記憶體,并對資料進行校驗、轉換解析和初始化,最終形成可以被虛拟機直接使用的java類型 . 這就是類加載機制 .

在java中,類型的加載、連接配接和初始化過程都是在程式運作期間完成的 . 在加載的過程中雖然會稍微增加一定的性能開銷 , 但是會為java程式提供更高的靈活性 . java中天生可以動态擴充的語言特性就是依賴運作期間動态加載和動态連結的特性實作的 .

當遇到以下5中情況時必須對類進行初始化(主動引用) .

1.  當new , getstatic , putstatic , invokestatic這4種情況下時 , 如類沒有進行初始化 , 則先觸發初始化.

最常見的場景就是 : 使用new關鍵字執行個體化對象的時候、讀取或者設定一個類的靜态字段(final修飾的或已在編譯器就放入常量池的字段除外),以及調用一個類的靜态方法

2.  使用java.lang.reflect包的方法進行反射調用的時候 .  如類沒有進行初始化 , 則先觸發初始化.

3. 初始化一個類的時候,若父類還未初始化 , 則先去初始化父類 . (接口不同 : 接口初始化時 , 并不要求父類接口全部完成初始化,隻有在真正使用到父接口的時候才開始初始化父類)

4. 虛拟機啟動時, 即項目啟動時 , 使用者需指定一個執行主類(包含main方法的類).虛拟機會先初始化這個類

5. 使用動态語言支援時 ,

注 : 以上情況被稱為主動引用 , 其他所有的方式都為被動引用 . 且不會觸發初始化 .

被動引用 :

1. 當使用子類直接調用父類的公有靜态字段時 , 隻有直接定義該字段的類[父類]才會初始化 , 子類不會初始化 .

2. 當定義引用類的數組時 , 不會觸發該類的初始化 .

3. 類中的常量(靜态final的)在編譯階段會存入靜态常量池中 , 本質上并沒有直接定義該常量的類 , 此時也不會觸發初始化 .

要清晰一點 : 加載是類加載過程中的一部分.該階段 , 虛拟機主要完成 :

a. 通過一個類的全限定類名(包名+類名)來擷取定義此類的二進制位元組流 .

b. 将這個位元組流所代表的靜态存儲結果轉化為方法區的運作時資料結構

c. 在記憶體中生成一個代表這個類的java對象 . 作為通路入口 .

虛拟機并不要求二進制流一定從.class檔案中擷取,且虛拟機并沒有指明要求從哪擷取 . 除從class檔案中擷取外 , 虛拟機也允許以下幾種情況 :

從如zip,jar,war包中擷取 .

通過代理的方式,如(java.lang.reflect.proxy 中使用proxygenrator.generateproxyclass為特定接口生成二進制位元組流)

由jsp檔案生成對應的class類

從資料庫中讀取 , 中間件伺服器中 , 可将特定程式安裝到資料庫中以達到代碼在叢集間的分發 .

注意 : 開發過程中 , 可通過重寫classloader的loadclass方法自定義自己的類加載器去控制擷取二進制位元組流的方式 .

數組本身也是一種引用類型 , 本身不通過類加載器建立 , 數組是通過虛拟機直接建立的 .但數組與類加載器仍有密切關系 , 因為數組的元素類型最終是依賴類加載器建立 .

一個數組類建立遵循以下原則 :

如果數組的元素類型是引用類型 , 則采用上述的加載過程加載元件類型,且數組将在加載該元件類型的類加載器的類名稱空間上被辨別.

若數組的元素類型不是引用類型而是基本類型,虛拟機會把數組标記為與引導加載器關聯

數組類的可見性與其元素類型的可見性一緻且元素不是引用類型 , 則數組類的可見性将預設為public

加載完成後 , 虛拟機外部的二進制位元組流将按照虛拟機所需格式存儲在方法區中 , ,然後在記憶體中執行個體化一個java對象

加載階段與連接配接階段的部分内容(如驗證)是交叉進行的 .

驗證是連接配接階段的第一步 , 為確定class檔案的位元組流中的資訊符合目前虛拟機的要求 . 且不會危害虛拟機自身安全需對資訊進行驗證 .

驗證階段主要完成以下4個階段的動作 :

這一步主要驗證位元組流是否符合class檔案格式規範及是否能被目前版本的虛拟機所處理 . 主要包括 :

是否以模數0xcafebabe開頭(每一個java位元組碼檔案(.class)都是以相同的4位元組内容開始的——十六進制的<code>cafebabe</code>)

主次版本号是否在目前虛拟機處理範圍内

常量池中的常量是否有不被支援的常量類型

指向常量的索引值是否有指向不存在的常量或不符合類型的常量

constant_utf-8_info中是否有不符合utf-8的編碼

class檔案中各個部分及檔案本身是否有被删除或附加的資訊

主要對位元組碼資訊進行語義分析,保證其資訊符合java規範,主要包括 :

驗證是否有父類

父類是否繼承了不被允許的類

是否實作了其父類或接口中要求實作的所有方法

字段/方法是否與父類相沖突

主要是對類的方法體進行檢驗分析 . 主要工作有 :

保證任意時刻操作的資料類型與指令代碼序列都能配合工作

保證跳轉指令不會跳轉到方法體以外的位元組碼指令上

保證方法體中的類型轉換是有效的 .

該動作将在連接配接的最後一個階段----解析時發生 .主要内容:

符号引用中通過字元串描述的全限類名是否能找到對應的類

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

符号引用中的類/方法/字段的通路性檢查 . 即引用的是否可以被目前類通路

該階段是正式為變量(僅包括static修飾的變量)配置設定記憶體并設定類變量初始值(為預設的零值)的階段 , 這些變量所使用的記憶體都将在方法區中進行配置設定 .

此處僅包括類中定義的被static修飾的變量,不包括執行個體變量 , 執行個體變量将會在對象執行個體化的時候随着對象一起被配置設定在java堆記憶體中 .

如 : 一個類中定義有public static int a = 100; 那麼在準備過後的a = 0 ; 而a = 100是在程式被編譯後 , 存放在類構造器&lt;clinit&gt;方法中.即a=100的指派是在初始化階段進行的

若a被final修飾符修飾 , 即: public static final int a = 100; 那麼該字段的屬性将存為constantvlaue屬性,在準備階段虛拟機就會為a指派為100.

該階段是虛拟機将常量池中的符号引用替換為直接引用的過程

以一組符号來描述所引用的目标 , 符号可以是任何形式的字面量.是要在使用時無歧義的可以定位到目标即可,其餘虛拟機的記憶體布局無關 .

是可以直接指向目标的指針,相對偏移量或者是能間接定位到目标的句柄 . 其和虛拟機的記憶體布局相關,如果有直接引用 , 那麼該引用的目标已經在記憶體中存在 .

類或者接口的解析

字段解析

類方法解析

接口方法解析

類加載的最後一步 , 将以上經過加載、驗證、準備、解析的位元組碼檔案加載為java對象 .

對于java中的每一個類 , 都需要加載它的類加載器和這個類本身一同确立其在java虛拟機中的唯一性. 每一個類加載器都有一個獨立的類名稱空間 .

即: 比較兩個類是否相等 , 隻有這兩個類是由同一個類加載器加載的前提下才有意義 . 否則即便同一個虛拟機環境下 , 兩個類也絕不相同 .

細分而言 , 類加載器分三種:

啟動類加載器 . 由c++實作 , 是虛拟機的一部分

bootstrapclassloader: 用c++編寫 , 嵌在jvm核心中的加載器 , 主要是負責加載java_home/lib下的類庫

擴充類加載器 ,

extensionclassloader: 用java編寫 , 器父類加載器是bootstrap主要是加載java_home/lib/ext目錄下的類庫

ava中系統屬性java.ext.dirs指定的目錄由extclassloader加載器加載,如果程式中沒有指定該系統屬性(-djava.ext.dirs=sss/lib)那麼該加載器預設加載$java_home/lib/ext目錄下的所有jar檔案,通過程式來看下系統變量java.ext.dirs所指定的路徑

應用程式類加載器

appclassloader: 加載classpath目錄下的所有的jar和class檔案 , 其父類加載器為ext classloader

原則 :

除了頂層的bootstrapclassloader外 , 所有的類加載器都會有自己的父類加載器 , 且加載器之間一般不會以繼承的方式實作 , 而是通過組合的方式 .

工作原理:

如果一個類加載器收到加載請求 , 先把請求委派給父類完成,是以所有的加載請求都會上傳到父類加載器中,隻有當父類加載器無法完成時 , 才有子類加載器嘗試自己去加載 .

優勢:

java類随着類加載器一起具備了一種帶優先級的層級關系