一.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。
? ? ? ?解析:把類中的符號引用轉(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。
靜態(tài)變量的聲明語句,以及靜態(tài)代碼塊都被看做類的初始化語句,Java虛擬機會按照初始化語句在類文件中的先后順序來依次執(zhí)行它們。例如當(dāng)以下Sample類被初始化后,它的靜態(tài)變量a的取值為4。
類初始化步驟:
-假如這個類還沒有被加載和連接,那就先進行加載和連接。
-假如類存在直接的父類,并且這個父類還沒有被初始化,那就先初始化直接的父類。
-假如類中存在初始化語句,那就依次執(zhí)行這些初始化語句。
當(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程序“首次主動使用”時才初始化它們。