java虛擬機具有語言無關係,它只和“class文件“這種特定的二進位文件格式綁定。 不同語言的編譯器將對應的程式編譯成位元組碼文件(*.class),送給jvm執行。 2.1、魔數(是否可以被虛擬機執行)和class文件版本 2.2、常量池 2.3、訪問標誌(識別訪問信息) 2.4、類索引、父類索引 ...
java虛擬機具有語言無關係,它只和“class文件“這種特定的二進位文件格式綁定。
不同語言的編譯器將對應的程式編譯成位元組碼文件(*.class),送給jvm執行。
- 2.1、魔數(是否可以被虛擬機執行)和class文件版本
- 2.2、常量池
- 2.3、訪問標誌(識別訪問信息)
- 2.4、類索引、父類索引和介面索引集合
- 2.5、欄位表集合
- 2.6、方法表集合
————————————————————————————————————————————————
一.類載入時機:
共5種情形為主動引用,有且僅有此5種會觸發初始化,其他方式全部為被動引用,不會觸發類的初始化
5種情形:
- 遇到new (實例化對象),getstatic(讀取一個類的靜態欄位) ,putstatic(設置一個類的靜態欄位), invokestatic(調用一個類的靜態方法)這4條指令,若類之前沒有初始化,需要先對其進行初始化。
- 使用 java.lang.reflect包的方法對類進行反射調用時,若類之前沒有初始化,需要先對其進行初始化。
- 當初始化一個類,其父類之前沒有初始化,需要先對其父類進行初始化。
- 當虛擬機啟動時,主類會先被初始化(包含main方法的類)。
- 使用jdk7的動態語言支持時,如果一個解析結果與靜態欄位或靜態方法有關,所對應的類之前沒有初始化,需要先對其進行初始化。
二.類載入過程
其中類載入的過程包括了載入、驗證、準備、解析、初始化五個階段。
在這五個階段中,載入、驗證、準備和初始化這四個階段發生的順序是確定的,而解析階段則不一定,它在某些情況下可以在初始化階段之後開始,這是為了支持Java語言的運行時綁定(動態綁定)。
另外註意這裡的幾個階段是按順序開始,而不是按順序進行或完成,因為這些階段通常都是互相交叉地混合進行的,通常在一個階段執行的過程中調用或激活另一個階段。
1.載入
載入過程完成一下3件事:
- 獲取一個類全限定名的二進位位元組流。
- 將靜態存儲結構轉換為方法區的運行時數據結構
- 記憶體中生產class對象,作為方法區中該類的數據訪問入口
載入與連接階段交叉進行(但是開始時間順序固定)。
2.驗證
四個階段:文件格式驗證(驗證規範),元數據驗證(語義校驗),位元組碼驗證(數據流與控制流分析),符號引用認證(符號引用的匹配校驗)。
3.準備:正式分配記憶體並設置變數初始值,記憶體在方法區內分配。
4.解析:將常量池內的符號引用替換為直接引用
符號引用:一組可以無歧義定位到目標的符號
直接引用:直接指向目標的指針,相對偏移量或者能間接定位到目標的句柄
5.初始化:根據程式制定的主管計划去初始化變數與資源。
三.類載入器
1.類與類載入器。每一個類載入器都有其獨立的類名稱空間,兩個類由同一個類載入器載入才可能相同,否則必然不同(equals,isAssignableFrom,isInstance)
2.雙親委派機制。
類載入器負責載入所有的類,其為所有被載入記憶體中的類生成一個java.lang.Class實例對象。一旦一個類被載入如JVM中,同一個類就不會被再次載入了。正如一個對象有一個唯一的標識一樣,一個載入JVM的類也有一個唯一的標識。在Java中,一個類用其全限定類名(包括包名和類名)作為標識;但在JVM中,一個類用其全限定類名和其類載入器作為其唯一標識。例如,如果在pg的包中有一個名為Person的類,被類載入器ClassLoader的實例kl負責載入,則該Person類對應的Class對象在JVM中表示為(Person.pg.kl)。這意味著兩個類載入器載入的同名類:(Person.pg.kl)和(Person.pg.kl2)是不同的、它們所載入的類也是完全不同、互不相容的。
JVM預定義有三種類載入器,當一個 JVM啟動的時候,Java開始使用如下三種類載入器:
1)根類載入器(bootstrap class loader):它用來載入 Java 的核心類,是用原生代碼來實現的,並不繼承自 java.lang.ClassLoader(負責載入$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++實現,不是ClassLoader子類)。由於引導類載入器涉及到虛擬機本地實現細節,開發者無法直接獲取到啟動類載入器的引用,所以不允許直接通過引用進行操作。
2)擴展類載入器(extensions class loader):它負責載入JRE的擴展目錄,lib/ext或者由java.ext.dirs系統屬性指定的目錄中的JAR包的類。由Java語言實現,父類載入器為null。
3)系統類載入器(system class loader):被稱為系統(也稱為應用)類載入器,它負責在JVM啟動時載入來自Java命令的-classpath選項、java.class.path系統屬性,或者CLASSPATH換將變數所指定的JAR包和類路徑。程式可以通過ClassLoader的靜態方法getSystemClassLoader()來獲取系統類載入器。如果沒有特別指定,則用戶自定義的類載入器都以此類載入器作為父載入器。由Java語言實現,父類載入器為ExtClassLoader。
類載入器載入Class大致要經過如下8個步驟:
- 檢測此Class是否載入過,即在緩衝區中是否有此Class,如果有直接進入第8步,否則進入第2步。
- 如果沒有父類載入器,則要麼Parent是根類載入器,要麼本身就是根類載入器,則跳到第4步,如果父類載入器存在,則進入第3步。
- 請求使用父類載入器去載入目標類,如果載入成功則跳至第8步,否則接著執行第5步。
- 請求使用根類載入器去載入目標類,如果載入成功則跳至第8步,否則跳至第7步。
- 當前類載入器嘗試尋找Class文件,如果找到則執行第6步,如果找不到則執行第7步。
- 從文件中載入Class,成功後跳至第8步。
- 拋出ClassNotFountException異常。
- 返回對應的java.lang.Class對象。
四、類載入機制:
1.JVM的類載入機制主要有如下3種。
- 全盤負責:所謂全盤負責,就是當一個類載入器負責載入某個Class時,該Class所依賴和引用其他Class也將由該類載入器負責載入,除非顯示使用另外一個類載入器來載入。
- 雙親委派:所謂的雙親委派,則是先讓父類載入器試圖載入該Class,只有在父類載入器無法載入該類時才嘗試從自己的類路徑中載入該類。通俗的講,就是某個特定的類載入器在接到載入類的請求時,首先將載入任務委托給父載入器,依次遞歸,如果父載入器可以完成類載入任務,就成功返回;只有父載入器無法完成此載入任務時,才自己去載入。
- 緩存機制。緩存機制將會保證所有載入過的Class都會被緩存,當程式中需要使用某個Class時,類載入器先從緩存區中搜尋該Class,只有當緩存區中不存在該Class對象時,系統才會讀取該類對應的二進位數據,並將其轉換成Class對象,存入緩衝區中。這就是為很麽修改了Class後,必須重新啟動JVM,程式所做的修改才會生效的原因。
2雙親委派機制:
雙親委派機制,其工作原理的是,如果一個類載入器收到了類載入請求,它並不會自己先去載入,而是把這個請求委托給父類的載入器去執行,如果父類載入器還存在其父類載入器,則進一步向上委托,依次遞歸,請求最終將到達頂層的啟動類載入器,如果父類載入器可以完成類載入任務,就成功返回,倘若父類載入器無法完成此載入任務,子載入器才會嘗試自己去載入,這就是雙親委派模式,即每個兒子都很懶,每次有活就丟給父親去乾,直到父親說這件事我也幹不了時,兒子自己才想辦法去完成。
雙親委派機制的優勢:採用雙親委派模式的是好處是Java類隨著它的類載入器一起具備了一種帶有優先順序的層次關係,通過這種層級關可以避免類的重覆載入,當父親已經載入了該類時,就沒有必要ClassLoader再載入一次。其次是考慮到安全因素,java核心api中定義類型不會被隨意替換,假設通過網路傳遞一個名為java.lang.Integer的類,通過雙親委托模式傳遞到啟動類載入器,而啟動類載入器在核心Java API發現這個名字的類,發現該類已被載入,並不會重新載入網路傳遞的過來的java.lang.Integer,而直接返回已載入過的Integer.class,這樣便可以防止核心API庫被隨意篡改。