<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                [TOC] 虛擬機把描述類的數據從Class文件**加載**到內存,并對數據進行**校驗**、轉換**解析**和**初始化**,最終成為被虛擬機直接使用的Java對象,這就是JVM的類加載機制。 ??? Java天生的可動態擴展的語言特性就是依賴運行期的**動態加載**和**動態連接**實現的。 </br> ## ??? 一:類的生命周期 ??? 類的生命周期包括7個部分:加載——驗證——準備——解析——初始化——使用——卸載 ??? 其中,驗證——準備——解析? 稱為連接階段。除了解析外,其他階段是順序發生的,而解析可以與這些階段交叉進行,因為Java支持動態綁定(晚期綁定),需要運行時才能確定具體類型。 ## ??? 二:類的初始化觸發 ??? 類的加載機制沒有明確的觸發條件,但是有5種情況下必須對類進行初始化,那么 加載——驗證——準備 就必須在此之前完成了。 ??? 1:new、getstatic、putstatic、invokestatic這4個? 字節碼指令? 時對類進行初始化(即:**實例化對象、讀寫靜態對象、調用靜態方法時,進行類的初始化**); ??? 2:使用反射機制對類進行調用時,進行類的初始化; ??? 3:初始化一個類,其父類沒有初始化時,先初始化其父類; ??? 4:虛擬機啟動時,初始化一個執行主類; ??? 5:使用JDK1.7的**動態語言**支持時,如果MethodHandle實例的解析結果為REF\_getstatic、REF\_putstatic、REF\_invokestatic的方法句柄(即:讀寫靜態對象或者調用靜態方法),則初始化該句柄對應類; ??? 一般,以上5種情況**最常見的是前三種:實例化對象、讀寫靜態對象、調用靜態方法、反射機制調用類、調用子類觸發父類初始化**。 ## ??? 三:類的加載過程 ??? 從用戶角度來說,類(對象)的生命周期只需籠統理解為“加載——使用——卸載”即可,無需太過深入。所以,這里的類加載過程就是我們說的 加載——驗證——準備——解析(非必須)——初始化? 這五個使用前的階段。 ### ??? 1:加載 ?????? 加載階段,虛擬機需要完成三件事:**通過類名字獲取類的二進制字節流——將字節流的內容轉存到方法區——在內存中生成一個Class對象作為該類方法區數據的訪問入口**。 ?????? 其中,第一步:通過類名獲取類的二進制字節流是通過類加載器來完成的。其加載過程使用“雙親委派模型”: ?????? 類加載器的層次結構為: ![](https://box.kancloud.cn/cdae2f27302b10cc7a1312d90089ab95_324x287.png) ?????? 啟動類加載器:加載系統環境變量下JAVA\_HOME/lib目錄下的類庫。 ?????? 擴展類加載器:加載JAVA\_HOME/lib/ext目錄下的類庫。 ?????? 應用程序類加載器(系統類加載器):加載用戶類路徑Class\_Path指定的類庫。(我們可以在使用第三方插件時,把jar包添加到ClassPath后就是使用了這個加載器) ?????? 自定義加載器:如果需要自定義加載時的規則(比如:指定類的字節流來源、動態加載時性能優化等),可以自己實現類加載器。 ?????? 雙親委派模型是指:當一個類加載器收到類加載請求時,不會直接加載這個類,而是把這個加載請求委派給自己父加載器去完成。如果父加載器無法加載時,子加載器才會去嘗試加載。 ?????? 采用雙親委派模型的原因:避免同一個類被多個類加載器重復加載。 ### ??? 2:驗證 ?????? 確保class文件的二進制字節流中包含的信息符號虛擬機要求,包括:文件格式驗證、元數據驗證(數據語義分析)、字節碼驗證(數據流語義合法性)、符號引用驗證(符號引用的匹配性校驗,確保解析能正確執行) ### ??? 3:準備 ?????? 為**類變量(靜態變量)**在**方法區**分配內存,并設置**零值**。注意:這里是類變量,不是實例變量,實例變量是對象分配到**堆內存**時根據運行時動態生成的。 ### ??? 4:解析 ?????? 把常量池中的符號引用解析為直接引用:根據符號引用所作的描述,在內存中找到符合描述的目標并把目標指針指針返回。 --- 關于符號引用與直接引用,我們還是用一個實例來分析吧。看下面的 Java 代碼: ``` package test; public class Test { public static void main(String[] args) { Sub sub = new Sub(); int a = 100; int d = sub.inc(a); } } class Sub { public int inc(int a) { return a + 2; } } ``` 編譯后使用 javap 分析工具,會得到下面的 Class 文件內容: ``` Constant pool: #1 = Methodref #6.#15 // java/lang/Object."":()V #2 = Class #16 // test/Sub #3 = Methodref #2.#15 // test/Sub."":()V #4 = Methodref #2.#17 // test/Sub.inc:(I)I #5 = Class #18 // test/Test #6 = Class #19 // java/lang/Object #7 = Utf8 #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 main #12 = Utf8 (\[Ljava/lang/String;)V #13 = Utf8 SourceFile #14 = Utf8 Test.java #15 = NameAndType #7:#8 // "":()V #16 = Utf8 test/Sub #17 = NameAndType #20:#21 // inc:(I)I #18 = Utf8 test/Test #19 = Utf8 java/lang/Object #20 = Utf8 inc #21 = Utf8 (I)I { public test.Test(); descriptor: ()V Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V Code: stack=2, locals=4, args_size=1 0: new #2 // class test/Sub 3: dup 4: invokespecial #3 // Method test/Sub."<init>":()V 7: astore_1 8: bipush 100 10: istore_2 11: aload_1 12: iload_2 13: invokevirtual #4 // Method test/Sub.inc:(I)I 16: istore_3 17: return } ``` 因為篇幅有限,上面的內容只保留了常量池,和 Code 部分。下面我們主要對 inc 方法的調用來進行說明。 **符號引用 ** 在 main 方法的字節碼中,調用 inc 方法的指令如下: ``` 13: invokevirtual #4 // Method test/Sub.inc:(I)I ``` invokevirtual 指令就是調用實例方法的指令,后面的操作數 4 是 Class 文件中常量池的下標,表示用來指定要調用的目標方法。我們再來看常量池在這個位置上的內容: ``` #4 = Methodref #2.#17 ``` 這是一個 Methodref 類型的數據,我們再來看看虛擬機規范中對該類型的說明: ``` CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; } ``` 這實際上就是一種引用類型,tag 表示了常量池數據類型,這里固定是 10。class_index 表示了類的索引,name_and_type_index 表示了名稱與類型的索引,這兩個也都是常量池的下標。在 javap 的輸出中,已經將對應的關系打印了出來,我們可以直接的觀察到它都引用了哪些類型: ``` #4 = Methodref #2.#17 // test/Sub.inc:(I)I |--#2 = Class #16 // test/Sub | |--#16 = Utf8 test/Sub |--#17 = NameAndType #20:#21 // inc:(I)I | |--#20 = Utf8 inc | |--#21 = Utf8 (I)I ``` 這里我們將其表現為樹的形式。可以看到,我們可以得到該方法所在的類,以及方法的名稱和描述符。于是我們根據 invokevirtual 的操作數,找到了常量池中方法對應的 Methodref,進而找到了方法所在的類以及方法的名稱和描述符,當然這些內容最終都是字符串形式。 實際上這就是一個符號引用的例子,符號引用也可以理解為像這樣使用文字形式來描述引用關系。 **直接引用 ** 符號引用在上面說完了,我們知道符號引用大概就是文字形式表示的引用關系。但是在方法的執行中,只有這樣一串字符串,有什么用呢?方法的本體在哪里?下面這就是直接引用的概念了,這里我用自己目前的理解總結一下,直接引用就是通過對符號引用進行解析,來獲得真正的函數入口地址,也就是在運行的內存區域找到該方法字節碼的起始位置,從而真正的調用方法。 那么將符號引用解析為直接引用的過程是什么樣的呢?我這個小渣渣目前也給不出確定的答案,在 JVM里的符號引用如何存儲? 里,RednaxelaFX 大大給出了一個 Sun JDK 1.0.2 的實現;在 自己動手寫Java虛擬機 中,作者給出了一種用 Go 的簡單實現,下面這里就來看一下這個簡單一些的實現。在 HotSpot VM 中的實現肯定要復雜得多,這里還是以大致的學習了解為主,以后如果有時間有精力,再去研究一下 OpenJDK 中 HotSpot VM 的實現。 不過不管是哪種實現,肯定要先讀取 Class 文件,然后將其以某種格式保存在內存中,類的數據會記錄在某個結構體內,方法的數據也會記錄在另外的結構體中,然后將結構體之間相互組合、關聯起來。比如,我們用下面的形式來表達 Class 的數據在內存中的保存形式: ``` type Class struct { accessFlags uint16 // 訪問控制 name string // 類名 superClassName string // 父類名 interfaceNames []string // 接口名列表 constantPool *ConstantPool // 該類對應的常量池 fields []*Field // 字段列表 methods []*Method // 方法列表 loader *ClassLoader // 加載該類的類加載器 superClass *Class // 父類結構體的引用 interfaces []*Class // 各個接口結構體的引用 instanceSlotCount uint // 類中的實例變量數量 staticSlotCount uint // 類中的靜態變量數量 staticVars Slots // 類中的靜態變量的引用列表 initStarted bool // 類是否被初始化 } ``` 類似的,常量池中的方法引用,也要有類似的結構來表示: ``` type MethodRef struct { cp *ConstantPool // 常量池 className string // 所在的類名 class *Class // 所在的類的結構體引用 name string // 方法名 descriptor string // 描述符 method *Method // 方法數據的引用 } ``` 回到上面符號解析的例子。當遇到 invokevirtual 指令時,根據后面的操作數,可以去常量池中指定位置取到方法引用的結構體。實際上這個結構體中已經包含了上面看到的各種符號引用,最下面的 method 就是真正的方法數據。類加載到內存中時,method 的值為空,當方法第一次調用時,會根據符號引用,找到方法的直接引用,并將值賦予 method。從而后面再次調用該方法時,只需要返回 method 即可。下面我們看方法的解析過程: ``` func (self *MethodRef) resolveMethodRef() { c := self.ResolvedClass() method := lookupMethod(c, self.name, self.descriptor) if method == nil { panic("java.lang.NoSuchMethodError") } self.method = method } ``` 這里面省略了驗證的部分,包括檢查解析后的方法是否為空、檢查當前類是否可以訪問該方法,等等。首先我們看到,第一步是找到方法對應的類: ``` func (self *SymRef) ResolvedClass() *Class { if self.class == nil { d := self.cp.class c := d.loader.LoadClass(self.className) self.class = c } return self.class } ``` 在 MethodRef 結構體中包含對應 class 的引用,如果 class 不為空,則可以直接返回;否則會根據類名,使用當前類的類加載器去嘗試加載這個類。最后將加載好的類引用賦給 MethodRef.class。找到了方法所在的類,下一步就是從類中找到這個方法,也就是方法數據在內存中的地址,對應上面的 lookupMethod 方法。查找時,會遍歷類中的方法列表,這塊在類加載的過程中已經完成,下面是方法數據的結構體: ``` type Method struct { accessFlags uint16 name string descriptor string class *Class maxStack uint maxLocals uint code []byte argSlotCount uint } ``` 這個其實就和 Class 文件中的 Code 屬性類似,這里面省略了異常和其他的一些信息。類加載過程中,會將各個方法的 Code 屬性按照上面的結構保存在內存中,然后將類中所有方法的地址列表保存在 Class 結構體中。當在 Class 結構體中查找指定方法時,只需要遍歷方法列表,然后比較方法名和描述符即可: ``` for c := class; c != nil; c = c.superClass { for _, method := range c.methods { if method.name == name && method.descriptor == descriptor { return method } } } ``` 可以看到,查找方法會從當前方法查找,如果找不到,會繼續從父類中查找。除此以外,還會從實現的接口列表中查找,代碼中省略了這部分,還有一些判斷的條件。 最終,如果成功找到了指定方法,就會將方法數據的地址賦給 MethodRef.method,后面對該方法的調用只需要直接返回 MethodRef.method 即可。 ### ??? 5:初始化 ?????? 真正開始執行Java程序代碼,該步執行方法根據代碼賦值語句,對**類變量和其他資源**? 進行初始化賦值。 ?????? 方法:編譯器自動收集類中所有? 類變量的賦值語句和靜態語句合并而成,收集的順序是在程序代碼出現的順序。所以,靜態語句中只能訪問到定義在靜態語句塊之前的變量,在其之后的變量可以賦值(相當于新建并賦值了)但不可以訪問(因為還沒出現)。 ?????? 注:由此步我們就可以得知,我們在分析向上轉型的例子時的程序代碼的運行順序了:父類靜態內容——子類靜態內容——父類構造——子類構造——子類方法 。 ??? 在經歷了上面5步“加載”階段后,才真正地可以使用class對象或者使用實例對象。使用過后,不再需要用到該類的class對象或者實例對象時,就會把類卸載掉(發生在方法區的垃圾回收:無用類的卸載)。 ## ? ? 四:對象的生命周期 ? ? ?對象是由類創建出來的,所以對象的生命周期就是包含在類的生命周期中: ? ? ?類加載(5步)——創建類的實例對象——使用對象——對象回收——類卸載
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看