jvm 深刻理解類加載機制

任何一個字節碼流能夠惟必定義一個類或接口,下文將字節碼流統稱爲字節碼文件java

類生命週期:加載,鏈接(驗證,準備,解析),初始化,使用,卸載 , 其中加載,驗證,準備,初始化,使用,卸載等步驟相對順序穩定,而解析階段能夠在初始化前進行操做,也能夠在初始化操做後真正使用符號引用再進行解析操做,也正是由於能夠在初始化後真正使用時進行解析操做,才能夠支持java的動態解析,如:直到調用對象的方法時,再去解析方法引用,這種狀況只有在運行時才能直到真正的調用對象數組

類加載過程:將指定環境下的字節碼文件加載至虛擬機內存,通過加載,鏈接,解析操做造成正確有效可用的java對象,注意:此處java對象並不是實例對象,而是指Class對象,用於定義java類元數據信息緩存

加載階段:包括三個步驟,1.將字節碼文件經過類加載器加載至虛擬機內存 2.將字節碼文件中靜態結構轉換爲方法區的運行時數據結構  3.根據方法區運行時數據結構構造對象元對象Class 安全

將字節碼文件加載至虛擬機內存,此項操做比較寬鬆,jvm並無規範從哪裏加載字節碼文件,因此咱們能夠從本地文件系統加載字節碼文件,也能夠經過網絡傳輸加載,還能夠從zip壓縮包中加載字節碼文件,甚至能夠從內存中計算生成字節碼流記載至虛擬機內存。加載完字節碼文件後,根據字節碼文件靜態數據結構將類信息,常量池等轉換爲方法區的運行時數據結構,最好根據方法區運行時數據結構生成類元對象Class,jvm並無規範Class對象必定要存放在堆區,各種虛擬機可自主實現Class對象的存儲位置,HotSpot虛擬機將Class對象存放在方法區網絡

鏈接階段:驗證,準備,解析數據結構

驗證:驗證操做並非等到加載操做徹底完成後纔開始執行,通常在進行類加載操做的時候會同步執行驗證操做,驗證操做具體包括:文件格式驗證,元數據驗證,字節碼驗證,符號引用驗證。文件格式驗證操做主要驗證字節碼文件是否符合jvm規範,好比文件魔術是否爲cafebabe,文件的主次版本是否能被本虛擬機接受執行等操做,確保字節碼執行不會危害虛擬機安全,雖然編譯過程會對一些有編譯錯誤的代碼拒絕編譯,可是因爲虛擬機並無規定字節碼文件的來源,咱們能夠用16進制編輯器編寫惡意代碼,若是沒有文件格式驗證,可能會危害虛擬機的自身安全。元數據驗證操做主要包括這個類是否有父類,除java.lang.Object每一個類都要有父類,且只能有一個父類,一個類是否繼承了不能被繼承final的父類,一個實現類是否實現抽象父類或父接口的抽象方法,是否重寫了父類的final方法等,確保類的元數據的正確性。字節碼驗證操做主要字節碼的語義,好比類型是否強制轉換爲不能強轉的類型,保證read,load操做相同類型,保證跳轉指令不會跳轉至方法體外的指令上去等操做。符號引用驗證操做主要符號引用的合法性,結合解析階段使用,確保符號引用能找到對應的類或接口,確保字段或方法的符號引用能找到對象的字段或方法,驗證對符號引用表示的類型的訪問權限等操做jvm

準備:在方法區爲字節碼流分配內存,在Class對象爲類字段賦初值,好比private static final a=100,此處在準備階段類變量被分配內存並賦初值a=0 注意:此處只對類變量賦初值,對實例變量階段不作處理,對常量會根據字段的ContantValue值對常量賦值,final int a=100 此處在準備階段a被賦值爲100編輯器

解析:將常量池的符號引用轉換爲直接引用,符號引用與本地機器無關,並且符號引用表示的類並不必定須要加載至內存,能夠直接使用符號引用表明某個類,並不要指向具體的目標代碼,但在使用的使用須要將符號引用表示的類加載至內存,直接內存與本地內存相關,能夠根據直接引用直接或間接定位到目標代碼,好比 java.long.Object符號引用表示指向Object類型的符號,此時Object類並不必定須要加載至內存,而將java.lang.Object轉換爲直接引用時,則java.lang.Object指向方法區Object類的地址。解析操做涉及常量池符號引用的解析,包括constant_class_info,constant_fieldref_info,constant_methodref_info,constant_interfacemethodref_info,constant_nameandtype_info,constant_methodtype_info,constant_methodhandle_info常量項的解析,除了constant_methodhandle_info常量項的解析操做,其餘類型的解析操做解析成功一次後會對解析結果進行緩存,提升下次解析的效率,而constant_methodtype_info自己就是動態類型,每次的解析結果都不同,則不會進行緩存,主要是類與接口的解析,字段解析,方法解析,接口方法解析,方法句柄解析。類與接口解析:若是對象類型爲非數組引用類型,那麼根據類或接口的全限定名經過類加載器加載至內存將符號引用轉換爲指向類內存結構的直接引用,若對象類型爲數組類型,那麼虛擬機直接構造數組類型內存結構,將符號引用指向內存數組對象的直接引用,而後判斷當前類對引用類型的訪問權限。字段解析:根據field_info中class_index屬性找到字段所屬類,若類不存在方法區,在進行類加載操做,若找不到類則直接失敗,若類class存在,則在class類中查找與field簡單名稱和字段描述符都相同的屬性(每一個類中存儲的字段都是本類聲明的字段,不包括從父類或父接口繼承來的字段),若是找到,則直接返回,若沒有,則在父接口中遞歸查找,找到則返回,若沒有,則再在父類中遞歸查找,若存在,則直接返回,若沒有則失敗。方法解析:方法解析與字段解析不一致,方法解析優先查本類,再遞歸查父類,最好遞歸查父接口,而字段解析中優先查本類,但再遞歸查父接口,最好遞歸查父類,方法解析其餘具體步驟與字段解析一致,只是在父類或父接口的前後遞歸查找順序不一致。接口方法解析獨立出來,與方法解析有點不同,由於接口方法只能出如今接口中,不會出如今類中,因此接口方法解析過程爲根據class_index查找類或接口,若是找到class爲類而不是接口時則直接失敗,不然在本接口查找與name_and_type_index相同的方法簡單名稱和描述符,若找到則直接返回,沒有則遞歸在父接口查找,若是找到則返回,沒有找到則解析失敗加密

初始化:初始化階段是類加載的最後一個階段,虛擬機根據代碼中靜態變量的賦值和靜態代碼塊的操做按代碼順序自動生成<cinit>類初始化方法,根據開發人員的自主定義初始化類變量,類,抽象類,甚至接口在進行類加載時,虛擬機都會自動生成<cinit>,不過接口中不容許有靜態代碼塊,也接口的類變量全是final常量,這些字段的賦值在準備階段已經完成,因此接口中<cinit>方法沒太大意義,因此<cinit>方法對類或接口並非必須的,若是類或接口既沒有靜態語句塊,也沒有類變量的賦值語句,則虛擬機不會去建立<cinit>方法,且爲了保證加載類的惟一性,虛擬機自動爲<cinit>添加同步語義,即一個線程在進行類初始化時,其餘對須要同一個類進行初始化操做的線程須要阻塞等待。jvm定義了有且只有下面5種狀況會觸發類初始化spa

1.實例化某類對象,訪問類的靜態字段,訪問類的靜態方法,即遇到new,putstatic,getstatic,invokestatic指令時若是類沒有進行初始化則會進行類初始化

2.經過反射實例化對象,訪問類的字段,方法時若是類沒有進行初始化則會進行初始化操做

3.進行子類初始化時,若是發現父類沒有進行初始化操做,則會先觸發父類的初始化操做(根據此條規則說明第一個初始化的類必定是Object,由於Object是全部引用類型的父類,包括數組類型)

4.虛擬機會自動執行運行主方法的類的初始化操做,即運行main方法的類運行以前會進行初始化操做

5.當使用jdk7動態語言支持時,若是constant_methodhandle_info常量項的解析結果爲ref_putstatic,ref_getstatic,ref_invokestatic時,若是解析結果對應的類沒有進行初始化時,則會觸發對應類的初始化操做

虛擬機規範代表有且只有上述五種狀況會進行類初始化操做,上述操做被稱爲主動引用,其餘一些方法看似調用了類的字段或方法,但不會觸發對應類的初始化,那些操做被稱爲被動引用。被動引用舉例:1.在子類中引用父類的類變量,通常來講子類會繼承父類的類變量,但經過子類訪問類變量時,只會觸發父類初始化,而不會觸發子類初始化  2.訪問某個類的常量屬性時,不會觸發類初始化,常量賦值在準備階段已經完成,引用常量字段可直接經過常量池訪問,不會觸發類初始化  3.初始化一個引用數組時,不會觸發數組元素引用類型的初始化,而是進行數組元素組成的數組類型的初始化,如 Person[] o=new Person[10],不會觸發person類初始化,而是觸發[com.test.Person 類初始化 

 

類加載器:類加載器主要進行類或接口加載過程,注意:數組類型直接由虛擬機進行加載,不是由類加載器加載,將特定環境下的字節碼流加載至內存,類加載器主要考慮的問題:在哪裏加載字節碼流?如何加載字節碼流,加載字節碼流前的操做?將字節碼流如何轉換成Class對象?基於前面的問題,類加載器的主要方法有findClass(),defineClass(),finfClass方法用於在何處找到字節碼流以及對字節碼流的後續處理,defineClass方法用於將字節碼流轉換爲Class對象。從何處加載字節碼流:能夠從本地文件系統直接加載.class字節碼流文件,也能夠從zip壓縮文件加載字節碼流,能夠從網絡傳輸中加載字節碼流,能夠內存中直接生成字節碼流。對應字節碼流的操做:加密解密操做。類加載器種類:啓動類加載器BootstrapClassLoader,擴展類加載器ExtClassLoader,應用類加載器AppClassLoader,自定義類加載器,啓動類加載器負責加載java_home/lib目錄下的,或者加載虛擬機參數 -Xbootclasspath參數目錄下的指定文件名錶示的class,好比rt.jar,一些非法不能識別的文件名即便在加載目錄下也不會被加載。ExtClassLoader擴展類加載器主要負責加載java_home/ext目錄下的class文件,AppClassLoader應用類加載器主要負責加載classpath目錄下的class文件,經過System.getProperty("java.class.path")可獲取classpath。因爲BootstrapClassLoader類加載器直接由C++代碼實現,並非ClassLoader子類,咱們不能控制BootstrapClassLoader加載,由虛擬機自動控制,其次須要注意的是:AppClassLoader類加載器是ExtClassLoader類加載器的子類

雙親委派模型:當一個類加載器被系統要求去加載某個class文件時,這個類加載器首先判斷本身是否有父類加載器,若是沒有則由BootstrapClassLoader啓動類加載器先進行類加載操做,若是有父類加載器,則先由父類加載器進行類加載操做,以上兩種狀況可將BootstrapClassLoader類加載器當作是全部類加載器的父類,即:能夠說成先由父類加載器嘗試加載類。若是父類加載器加載成功,則直接返回父類加載器加載的類,不然子類加載器自己進行加載類操做,若是加載成功,則返回自身加載的類,不然返回類加載失敗。爲何要根據雙親委派模型進行類加載操做?保證加載類的全局惟一性,即父類加載器負責目錄下的class文件不會被子類相同類型的class文件所覆蓋,好比你能夠在classpath目錄下建立一個java.lang.Object字節碼文件,根據雙親委派模型,會先由啓動類加載器加載java_home/lin目錄下的java.lang.Object字節碼文件,不會去加載自主定義的字節碼文件

相關文章
相關標籤/搜索