JVM學(xué)習(xí)筆記(二)

一.JVM與程序的生命周期

Java虛擬機結(jié)束生命周期:

①執(zhí)行了System.exit()方法。

②程序正常執(zhí)行結(jié)束。

③程序在執(zhí)行過程遇到異?;蝈e誤而異常終止。

④由于操作系統(tǒng)出現(xiàn)錯誤而導(dǎo)致Java虛擬機進程終止。

二.類的加載過程

1.類的加載、連接與初始化

類的加載、連接和初始化

①加載:

? ? ? ?查找并加載類的二進制數(shù)據(jù)。將字節(jié)碼文件中二進制數(shù)據(jù)讀入到內(nèi)存中,把其放在運行時數(shù)據(jù)區(qū)的方法區(qū)內(nèi),然后在堆區(qū)創(chuàng)建一個java.lang.Class對象,用來封裝在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)。

? ? ? 加載字節(jié)碼文件方式,從本地系統(tǒng)中直接加載,從網(wǎng)絡(luò)下載字節(jié)碼文件,從zip、jar等歸檔文件中加載字節(jié)碼文件,從專有數(shù)據(jù)庫中提取字節(jié)碼文件,將java源文件動態(tài)編譯為.class文件。

類的加載

? ? ? 類的加載的最終產(chǎn)品是位于堆區(qū)中的Class對象,Class對象封裝了類的方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu),并且向Java程序員提供方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)的接口。

? ? ? 類加載器的兩種類型,java虛擬機自帶的加載器(根類加載器、擴展類加載器、系統(tǒng)類加載器),用戶自定義的類加載器(java.lang.ClassLoader的子類、用戶可以定制類的加載方式)。

? ? ? 類加載器并不需要等到某個類被“首次主動使用”時再加載它。

? ? ? JVM規(guī)范允許類加載器在預(yù)料某個類將要被使用時就預(yù)先加載它,如果在預(yù)先加載的過程中遇到了.class文件缺失或存在錯誤,類加載器必須在程序首次主動使用該類時才報告錯誤(LinkageError錯誤),如果這個類一直沒有被程序主動使用,那么類加載器就不會報告錯誤。

②連接

類被加載后,就進入連接階段。連接就是將已經(jīng)讀入到內(nèi)存的類的二進制數(shù)據(jù)合并到虛擬機的運行時環(huán)境中去。

? ? ? ?驗證:確保被加載的類的正確性。驗證的內(nèi)容包括類文件的結(jié)構(gòu)檢查,語義檢查,字節(jié)碼驗證,二進制兼容性的驗證。

? ? ? ? ? ? -類文件的結(jié)構(gòu)檢查:確保類文件遵從Java類文件的固定格式。

? ? ? ? ? ? -語義檢查:確保類本身符合Java語言的語法規(guī)定,比如驗證final類型的類沒有子類,以及final類型的方法沒有被覆蓋。

? ? ? ? ? ? -字節(jié)碼驗證:確保字節(jié)碼流可以被Java虛擬機安全的執(zhí)行。字節(jié)碼流代表Java方法(包括靜態(tài)方法和實例方法),它是由被稱做操作碼的單字節(jié)指令組成的序列,每一個操作碼后都跟著一個或多個操作數(shù)。字節(jié)碼驗證步驟檢查每個操作碼是否合法,即是否有著合法的操作數(shù)。

? ? ? ? ? ? -二進制兼容的驗證:確保相互的類之間協(xié)調(diào)一致。例如在Worker類的gotoWork()方法中會調(diào)用Car類的run()方法。Java虛擬機在驗證Worker類時,會檢查在方法區(qū)內(nèi)是否存在Car類的run()方法,假如不存在(當(dāng)Worker類和Car類的版本不兼容,就會出現(xiàn)這種問題),就會拋出NosuchMethodError錯誤。

? ? ? ?準(zhǔn)備:為類的靜態(tài)變量分配內(nèi)存,并將其初始化為默認(rèn)值。

在準(zhǔn)備階段,Java虛擬機為類的靜態(tài)變量分配內(nèi)存,并設(shè)置默認(rèn)的初始值。例如對于以下Sample類,在準(zhǔn)備階段,將int類型的靜態(tài)變量a分配4個字節(jié)的內(nèi)存空間,并且賦予默認(rèn)值0,為long類型的靜態(tài)變量b分配8個字節(jié)的內(nèi)存空間,并且默認(rèn)值0。

Sample類

? ? ? ?解析:把類中的符號引用轉(zhuǎn)換為直接引用。

在解析階段,Java虛擬機會把類的二進制數(shù)據(jù)中的符號引用替換為直接引用。例如在Worker類的gotoWork()方法中會引用Car類的run()方法。

連接中的解析

在Worker類的二進制數(shù)據(jù)中,包含了一個對Car類的run()方法的符號引用,它由run()方法的全名和相關(guān)描述符組成。在解析階段,Java虛擬機會把這個符號引用替換為一個指針,該指針指向Car類的run()方法在方法區(qū)內(nèi)的內(nèi)存位置,這個指針就是直接引用。

③初始化:為類的靜態(tài)變量賦予正確的初始值。

在初始化階段,Java虛擬機執(zhí)行類的初始化語句,為類的靜態(tài)變量賦予初始值。在程序中,靜態(tài)變量的初始化有兩種途徑:(1)在靜態(tài)變量的聲明處進行初始化;(2)在靜態(tài)代碼塊中進行初始化。例如在以下代碼中,靜態(tài)變量a和b都被顯示初始化,而靜態(tài)變量c沒有被明顯初始化,它將保持默認(rèn)值0。

Sample類

靜態(tài)變量的聲明語句,以及靜態(tài)代碼塊都被看做類的初始化語句,Java虛擬機會按照初始化語句在類文件中的先后順序來依次執(zhí)行它們。例如當(dāng)以下Sample類被初始化后,它的靜態(tài)變量a的取值為4。

Sample類

類初始化步驟:

-假如這個類還沒有被加載和連接,那就先進行加載和連接。

-假如類存在直接的父類,并且這個父類還沒有被初始化,那就先初始化直接的父類。

-假如類中存在初始化語句,那就依次執(zhí)行這些初始化語句。

類ExtendsDemo2
ExtendsDemo2運行結(jié)果

當(dāng)Java虛擬機初始化一個類時,要求它的所有父類都已經(jīng)被初始化,但是這條規(guī)則并不適用于接口。在初始化一個類時,并不會先初始化它所實現(xiàn)的接口;在初始化一個接口時,并不會先初始化它的父接口。因此,一個父接口并不會因為它的子接口或者實現(xiàn)類的初始化而初始化。只有當(dāng)程序首次使用特定接口的靜態(tài)變量時,才會導(dǎo)致接口的初始化。

只有當(dāng)程序訪問的靜態(tài)變量或靜態(tài)方法確實在當(dāng)前類或當(dāng)前接口中定義時,才可以認(rèn)為是對類或接口的主動使用。調(diào)用ClassLoader類的loadClass方法加載一個類,并不是對類的主動使用,不會導(dǎo)致類的初始化。

2.Java程序?qū)︻惖氖褂梅绞娇煞譃閮煞N

①主動使用(六種)

-創(chuàng)建類的實例

-訪問某個類或接口的靜態(tài)變量,或者對該靜態(tài)變量賦值

-調(diào)用類的靜態(tài)方法

-反射(如Class.forName("com.mysql.jdbc.Driver"))

-初始化一個類的子類

-Java虛擬機啟動時被標(biāo)明為啟動的類(就是含有main方法,程序入口的類)

②被動使用:除了以上6種主動使用外,其他都是被動使用,都不會導(dǎo)致類的初始化。

注意:所有的Java虛擬機實現(xiàn)必須在每個類或接口被Java程序“首次主動使用”時才初始化它們。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容