Java虛擬機(jī)整體篇幅如下:
本片文章內(nèi)容如下:
- 1、類加載器
- 2、"類"的生命周期
- 3、一個(gè)類載入過(guò)程
- 4、"類"結(jié)束生命周期
- 5、new 一個(gè)對(duì)象過(guò)程
- 6、總結(jié)
一、類加載器
(一)、概述
首先來(lái)看一下java程序的執(zhí)行過(guò)程。如下圖:
在這個(gè)框架圖很容易大體上了解Java程序工作原理。首先當(dāng)程序員寫(xiě)好.java文件后,需要先運(yùn)行(假設(shè)該文件為demo.java)
javac demo.java
此時(shí),你的Java代碼就被編譯成字節(jié)碼(.class),如果你是在IDE開(kāi)發(fā)工具中,你保存代碼的時(shí)候,開(kāi)發(fā)工具已經(jīng)幫你完成了上述的編譯工作,因此你可以在對(duì)應(yīng)的目錄下看到class文件。此時(shí)class文件依然保存在硬盤中,因此,當(dāng)你在命令行中運(yùn)行
java demo
就完成了上面紅色框中的工作,JRE的加載器從硬盤中讀取class文件,載入到系統(tǒng)分配給JVM的內(nèi)存區(qū)域——運(yùn)行時(shí)數(shù)據(jù)區(qū)(Runtime Data Areas),然后執(zhí)行引擎解釋或者編譯類文件,轉(zhuǎn)化為特定的CPU機(jī)器碼,CPU執(zhí)行機(jī)器碼,至此完成整個(gè)過(guò)程。
下面就來(lái)研究一下類加載器是什么東西?又是如何工作的?
(二)、層級(jí)關(guān)系
類加載器被組織成一種層級(jí)結(jié)構(gòu)關(guān)系,也就是父子關(guān)系。其中Bootstrap是所有類加載器的父親,如下圖:
我們先來(lái)簡(jiǎn)單介紹下上面涉及到的幾個(gè)class Loader
- Bootstrap class loader:
當(dāng)運(yùn)行Java虛擬機(jī)時(shí),這個(gè)類加載器被創(chuàng)建,它加載了一些基本的Java API,包括Object這個(gè)類。需要注意的是,這個(gè)類加載器不是用Java語(yǔ)言寫(xiě)的,而是用C/C++寫(xiě)的。- Extension class loader:
這個(gè)類加載器出了基本API之外的一些拓展類,包括一些與安全性能相關(guān)的類。- System Class Loader:
它加載應(yīng)用程序中的類,也就是在你的classpath中配置的類。- User-Defined Class Loader:
這是開(kāi)發(fā)人員通過(guò)拓展ClassLoader類定義的自定義加載類,加載程序員定義的一些類。
(三)、委派模式
請(qǐng)參考Android插件化基礎(chǔ)1-----加載SD上APK中的"雙親委托"
二、一個(gè)類生命周期
類從被加載到虛擬機(jī)內(nèi)存中開(kāi)始,直到卸載出內(nèi)存為止,它的整個(gè)生命周期包括了:加載、驗(yàn)證、準(zhǔn)備、解析、初始化、使用和卸載7個(gè)階段,其中驗(yàn)證、準(zhǔn)備和解析這是三個(gè)部分統(tǒng)稱為連接(linking)。如下圖:
其中,加載、驗(yàn)證、準(zhǔn)備、初始化和卸載這5個(gè)階段的順序是確定的,類的加載過(guò)程必須按照這種順序按部就班的"開(kāi)始"(僅僅指的是開(kāi)始,而非執(zhí)行或者結(jié)束,因?yàn)檫@些階段通常都是相互交叉的混合進(jìn)行,通常會(huì)在一個(gè)階段執(zhí)行的過(guò)程中調(diào)用或者激活另一個(gè)階段),而解析階段則不一定(它在某些情況下可以在初始化階段之后再開(kāi)始),這是為了支持Java語(yǔ)言的運(yùn)行時(shí)綁定
三、一個(gè)類載入過(guò)程
通過(guò)上面的內(nèi)容我們知道,一個(gè)類的加載過(guò)程被分為5個(gè)階段:加載、驗(yàn)證、準(zhǔn)備、解析、初始化。如下圖
下面我們就詳細(xì)講解下
(一)、"加載"階段
類的裝載指的是將類的.class文件中的二進(jìn)制數(shù)據(jù)讀到內(nèi)存中,將其放在運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi),讓后在Java堆創(chuàng)建一個(gè)java.lang.Class對(duì)象,用來(lái)封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)。類的加載的最終產(chǎn)品是位于Java堆中的Class對(duì)象,Class對(duì)象封裝了類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu),并向Java程序員提供了訪問(wèn)方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)的接口。
"加載(loading)"階段是"類加載(Class Loading)"過(guò)程的第一個(gè)階段,在此階段,虛擬機(jī)需要完成以下三件事:
- 1、加載二進(jìn)制字節(jié)流:
通過(guò)一個(gè)類的全限定名(包名+類名)嗎,來(lái)獲取此類的二進(jìn)制字節(jié)流,虛擬機(jī)規(guī)范沒(méi)有指定二進(jìn)制字節(jié)流從哪里讀取,可以是class文件,可以是jar,也可以是由動(dòng)態(tài)代理在運(yùn)行時(shí)生成,等等,只要符合規(guī)范的字節(jié)流即可,由類加載器來(lái)決定字節(jié)流的來(lái)源、- 2、生成方法區(qū)的數(shù)據(jù)結(jié)構(gòu):
將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)- 3、創(chuàng)建Class實(shí)例
在Java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這些數(shù)據(jù)的訪問(wèn)入口。
加載階段即可以使用系統(tǒng)提供的類加載器來(lái)完成,也可以由用戶自定義的類加載器來(lái)完成。加載階段與連接階段的部分內(nèi)容(如一部分字節(jié)碼格式驗(yàn)證動(dòng)作)是交叉進(jìn)行的,加載階段尚未完成,連接可能已經(jīng)開(kāi)始。
加載.class文件的方式有:
- 從本地系統(tǒng)中直接加載
- 通過(guò)網(wǎng)絡(luò)下載.class文件
- 從zip、jar等歸檔文件中加載.class文件
- 從專有數(shù)據(jù)庫(kù)中提取.class文件
- 將Java源文件動(dòng)態(tài)編譯為.class文件
加載階段完成后,虛擬機(jī)外部的二進(jìn)制字節(jié)流就按照虛擬機(jī)所需的格式存儲(chǔ)在方法區(qū)之中,而且在Java堆中也創(chuàng)建一個(gè)java.lang.Class類的對(duì)象,這樣便可以通過(guò)該對(duì)象訪問(wèn)方法區(qū)中的這些數(shù)據(jù)。
(二)、"驗(yàn)證"階段
驗(yàn)證是連接階段的第一步,這一階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。
Java語(yǔ)言本身是相對(duì)安全的語(yǔ)言,使用Java編碼是無(wú)法做到如訪問(wèn)數(shù)組邊界意外的數(shù)據(jù)、將一個(gè)對(duì)象轉(zhuǎn)型為它并且未實(shí)現(xiàn)的類型等,如果這樣做了,編譯器將拒絕編譯。但是,Class文件并不一定是由Java源碼編譯而來(lái),可以使用任何途徑,包括16進(jìn)制編譯器(如 UItraEdit)直接編寫(xiě)。如果直接編寫(xiě)了有害的"代碼"(字節(jié)流),而虛擬機(jī)在加載該Class時(shí)不進(jìn)行檢查的話,就有可能危害到虛擬機(jī)或者程序的安全。
不同的虛擬機(jī),對(duì)類驗(yàn)證的實(shí)現(xiàn)可能有所不同,但大致都會(huì)完成下面的四個(gè)階段的驗(yàn)證:
- 文件格式驗(yàn)證
- 元數(shù)據(jù)驗(yàn)證
- 字節(jié)碼驗(yàn)證
- 符號(hào)引用驗(yàn)證
那么下面我們就來(lái)一一介紹
1、文件格式驗(yàn)證
是要驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機(jī)處理。
- 驗(yàn)證魔數(shù)是否是0xCAFEBABE;
- 主、次版本號(hào)是否正在當(dāng)前虛擬機(jī)處理范圍內(nèi);
- 常量池的常量是否有不被支持的常量類型等,該驗(yàn)證階段的主要目的是保證輸入的字節(jié)流能正確地解析并存儲(chǔ)方法區(qū)中
- 常量池里的項(xiàng)是否執(zhí)行不存在的常量或者不符合類型的常量
經(jīng)過(guò)這個(gè)階段的驗(yàn)證后,字節(jié)流才會(huì)進(jìn)入內(nèi)存的方法區(qū)中存儲(chǔ),所以后面的三個(gè)驗(yàn)證階段都是基于方法區(qū)的存儲(chǔ)結(jié)構(gòu)進(jìn)行的。
更詳細(xì)的關(guān)于.class文件的格式的內(nèi)容,請(qǐng)參考Java字節(jié)碼(.class文件)格式詳解(一)
2、元數(shù)據(jù)驗(yàn)證
對(duì)字節(jié)碼描述的信息進(jìn)行語(yǔ)義分析,以保證其描述的信息符合Java語(yǔ)言規(guī)范的要求,可能包括的驗(yàn)證如:這個(gè)類是否有父類;這個(gè)類的父類是否繼承了不允許被繼承的類;如果這個(gè)類不是抽象類,是否實(shí)現(xiàn)了其父類或者接口中要求的所有方法等。
3、字節(jié)碼驗(yàn)證
主要工作是進(jìn)行數(shù)據(jù)流和控制流分析,寶貝被校驗(yàn)類的方法在運(yùn)行時(shí)不會(huì)做出危害虛擬機(jī)安全的行為。如果一個(gè)類方法體的字節(jié)碼沒(méi)有通過(guò)字節(jié)碼驗(yàn)證,那肯定是有問(wèn)題的;但如果一個(gè)方法體通過(guò)了字節(jié)碼驗(yàn)證,也不能說(shuō)明其一定就是安全的。
4、符號(hào)引用驗(yàn)證
發(fā)生在虛擬機(jī)將符號(hào)引用轉(zhuǎn)化為直接引用的時(shí)候,這個(gè)轉(zhuǎn)化動(dòng)作將在"解析階段"中發(fā)生。驗(yàn)證符號(hào)引用通過(guò)字符串描述的權(quán)限定名是否能找到對(duì)應(yīng)的類;在指定類中是否存在符合方法字段的描述符及簡(jiǎn)單名稱所描述的方法和字段;符號(hào)引用中的類、字段和方法的訪問(wèn)性(private、protected、public、default)是否可被當(dāng)前類訪問(wèn)。
如果無(wú)法通過(guò)符號(hào)引用驗(yàn)證,將拋出一個(gè)java.lang.IncompatibleClassChangeError的子類
驗(yàn)證階段對(duì)于虛擬機(jī)的類加載機(jī)制來(lái)說(shuō),不一定是必須要的階段。如果所運(yùn)行的全部代碼確認(rèn)是安全的。
可以使用-Xverify:none參數(shù)來(lái)關(guān)閉大部分的類驗(yàn)證措施,以縮短虛擬機(jī)類加載時(shí)間。
(三)、"準(zhǔn)備"階段
準(zhǔn)備階段是為類的靜態(tài)變量分配內(nèi)存并將其初始化為默認(rèn)值,這些內(nèi)存都將在方法區(qū)進(jìn)行分配。準(zhǔn)備階段不分配類中的實(shí)例變量的內(nèi)存,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配到Java堆中。
比如:
public static int value=100
在"準(zhǔn)備"階段的時(shí)候value初始值為0,"初始化"階段才會(huì)變?yōu)?00
但是,對(duì)于static final field,此階段是直接賦值的。
比如:
private static final int value=100;
在"準(zhǔn)備"階段的時(shí)候value初始值為100
Java虛擬機(jī)中各種類型的默認(rèn)初始值。
數(shù)據(jù)類型 | 默認(rèn)初始值 |
---|---|
int | 0 |
long | 0L |
short | (short)0 |
byte | (byte)0 |
short | '\u0000' |
boolean | false |
float | 0.0f |
double | 0.0d |
reference | null |
(四)、"解析"階段
解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程。
1、符號(hào)引用(Symbolic References)與直接引用(Direct Reference)
可能有同學(xué)不了解符號(hào)引用和直接引用,我們就在這里簡(jiǎn)單介紹下:
- 符號(hào)引用:
符號(hào)引用以一組符號(hào)來(lái)描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量,只要使用能夠無(wú)歧義的定位到目標(biāo)即可。例如,在Class文件中它以CONSTANT_CLASS_INFO、CONSTANT_FIELDREF_INFO、CONSTANT_METHODREF_INFO等類型的常量出現(xiàn)。符號(hào)引用與虛擬機(jī)的內(nèi)存布局無(wú)關(guān),引用的目標(biāo)并不是一定加載到內(nèi)存中。在Java中,一個(gè)java類會(huì)編譯.class文件。在編譯時(shí),java類并不知道所引用類的實(shí)際地址,因此只能使用符號(hào)引用來(lái)代替。比如com.test.Human類引用了com.test.Car類,在編譯時(shí)Human類并不知道Car的實(shí)際內(nèi)存地址,因此只能使用符號(hào)com.test.Car(假設(shè)是這個(gè),當(dāng)然實(shí)際中由類似于CONSTANT_CLASS_INFO的常量來(lái)標(biāo)識(shí)的)來(lái)標(biāo)識(shí)Car類的地址。各種虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局可能不同,但是它們能接受的符號(hào)引用都是一致的,因?yàn)榉?hào)引用的字面量形式明確定義在Java虛擬機(jī)規(guī)范的Class文件格式中。- 直接引用:直接引用可以是
- 直接指向目標(biāo)的指針(比如,指向"類型(Class對(duì)象)"、類變量、類方法的直接引用可能是指向方法區(qū)的指針)
- 相對(duì)偏移量(比如,指向?qū)嶓w變量、實(shí)例方法的直接引用都是偏移量)
- 一個(gè)能間接定位到目標(biāo)的句柄。
直接引用是和虛擬機(jī)布局相關(guān)的,同一個(gè)符號(hào)在不同的虛擬機(jī)實(shí)例上翻譯出來(lái)的直接引用一般不會(huì)相同。如果有了直接引用,那引用的目標(biāo)必定已經(jīng)被加載到內(nèi)存中了。
上面說(shuō)的東西有點(diǎn)"空",不好理解,那我們舉例說(shuō)明:
在java中,一個(gè)java類將會(huì)編譯成一個(gè)class文件。在編譯時(shí),java類并不知道引用類的實(shí)際內(nèi)存地址,因此只能使用符號(hào)引用來(lái)代替。比如com.demo.People類引用com.demo.Tool類,在編譯時(shí)People類并不知道Tool類的實(shí)際內(nèi)存地址,因此只能使用符號(hào)com.demo.Tool(假設(shè))來(lái)表示Tool類的地址。而在類裝載器裝載People類時(shí),此時(shí)可以通過(guò)虛擬機(jī)獲取Tool類 的實(shí)際內(nèi)存地址,因此便可以既將符號(hào)com.demo.Tool替換為Tool類的實(shí)際內(nèi)存地址,及直接引用地址。
2、解析類或者接口
3、解析字段
4、解析類方法
5、解析接口方法
(五)、"初始化"階段
類初始化時(shí)類加載過(guò)程的最后一步,前面的類加載過(guò)程,除了在加載階段用戶應(yīng)用程序可以通過(guò)自定義類加載器參與外,其余動(dòng)作完全由虛擬機(jī)主導(dǎo)和控制,到了初始化階段,才真正開(kāi)始執(zhí)行類中定義的Java程序代碼。在準(zhǔn)備階段,類變量已經(jīng)被賦過(guò)一次系統(tǒng)要求的初始值,而在初始化階段,則是根據(jù)程序員通過(guò)程序指定的主觀計(jì)劃去初始化類變量和其他資源,或者可以從另一個(gè)角度來(lái)表達(dá):初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過(guò)程。
1、<clinit>與<init>
Java在編譯生成.class文件時(shí),會(huì)自動(dòng)產(chǎn)生兩個(gè)方法,一個(gè)是類的初始化方法<clinit>。另一個(gè)是實(shí)例的初始化方法<init>。
<clinit>與<init>的區(qū)別
- 這兩個(gè)方法一個(gè)是虛擬機(jī)在裝載一個(gè)類初始化的時(shí)候調(diào)用——<clinit>。另一個(gè)是在類實(shí)例化的時(shí)候調(diào)用的——<init>。
- 所有的類變量初始化語(yǔ)句和類靜態(tài)初始化語(yǔ)句都被Java編譯器收集到了一起,放在一個(gè)特殊的方法中。這個(gè)方法就是——<clinit>
- <init>方法時(shí)在一個(gè)類進(jìn)行對(duì)象實(shí)例化時(shí)調(diào)用的。實(shí)例化一個(gè)類有四種途徑:
- 調(diào)用new操作符;
- 調(diào)用Class或java.lang.reflect.Constructor對(duì)象的newInstance()方法;
- 調(diào)用任何現(xiàn)有對(duì)象的clone()方法;
- 通過(guò)java.io.ObjectInputStream類的getObject()進(jìn)行反序列化
所以Java編譯器為它的每一個(gè)類都至少生成一個(gè)實(shí)例初始化方法。一個(gè)用于初始化靜態(tài)的類變量,一個(gè)是初始化實(shí)例變量。
2、<clinit>()方法的執(zhí)行規(guī)則:
- 1、<clinit>()方法時(shí)由編譯器自動(dòng)收集類中的所有變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊中的語(yǔ)句合并產(chǎn)生的,編譯器收集的順序是由語(yǔ)句在源文件中出現(xiàn)的順序所決定的,靜態(tài)語(yǔ)句塊中只能訪問(wèn)到定義在靜態(tài)語(yǔ)句塊之前的變量,定義在它之后的變量,在前面靜態(tài)語(yǔ)句中可以賦值,但不能訪問(wèn)。
- 2、<clinit>()方法與實(shí)例構(gòu)造器<init>()方法(類的構(gòu)造器)不同,它不需要顯式地調(diào)用父類構(gòu)造器,虛擬機(jī)會(huì)保證在子類的<clinit>()方法執(zhí)行之前,父類的<clinit>()方法已經(jīng)執(zhí)行完畢。因此,在虛擬機(jī)會(huì)保證在子類的<clinit>()方法執(zhí)行之前,父類的<clinit>()方法已經(jīng)執(zhí)行完畢。因此,在虛擬機(jī)中第一個(gè)被執(zhí)行的<clinit>()方法的類肯定是java.lang.Object。
- 3、<clinit>()方法對(duì)于類或接口來(lái)說(shuō)并不是必須的,如果一個(gè)類中沒(méi)有靜態(tài)語(yǔ)句塊,也沒(méi)有對(duì)類變量的賦值操作,那么編譯器可以不為這個(gè)類生成<clinit>()方法。
- 4、接口中不能使用靜態(tài)語(yǔ)句塊,但仍然有類變量(final static) 初始化的賦值操作,因此接口與類一樣會(huì)生成<clinit>()方法。但是接口類不同的是:執(zhí)行接口的<clinit>()方法不需要先執(zhí)行父接口的<clinit>()方法,只有當(dāng)父接口中定義的變量被使用時(shí),父接口才會(huì)被初始化。另外,接口的實(shí)現(xiàn)類在初始化時(shí)也一樣不會(huì)執(zhí)行接口的<clinit>()方法。
- 5、虛擬機(jī)會(huì)保證一個(gè)類的<clinit>()方法在多線程環(huán)境中被正確地加鎖和同步,如果多個(gè)線程同時(shí)去初始化一個(gè)類,那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類的<clinit>()方法,其線程都需要阻塞等待,直到活動(dòng)線程執(zhí)行<clinit>()方法完畢。如果在一個(gè)類的<clinit>()方法中有耗時(shí)很長(zhǎng)的操作,那就可能造成多個(gè)線程阻塞,在實(shí)際引用中這種阻塞往往是很隱蔽的。
3、主動(dòng)引用和被動(dòng)引用:
在準(zhǔn)備階段,變量已經(jīng)賦過(guò)一次系統(tǒng)要去的初始值,在初始化階段,則是根據(jù)程序員通過(guò)程序的主管計(jì)劃區(qū)初始化類變量和其他資源。Java虛擬機(jī)規(guī)范了4種情況必須立即對(duì)類進(jìn)行初始化(加載、驗(yàn)證、準(zhǔn)備必須在此之前完成)
- 1、當(dāng)使用new關(guān)鍵字實(shí)例化對(duì)象時(shí),當(dāng)讀取或者設(shè)置一個(gè)類的靜態(tài)字段(被final修飾的除外)時(shí),以及當(dāng)調(diào)用一個(gè)類的靜態(tài)方法時(shí)(比如構(gòu)造方法就是靜態(tài)方法),如果類未初始化,則需要先初始化。
- 2、當(dāng)通過(guò)反射機(jī)制對(duì)類進(jìn)行調(diào)用時(shí),如果類未初始化,則需要先初始化。
- 3、當(dāng)初始化一個(gè)類時(shí),如果其父類未初始化,先初始化父類。
- 4、用戶指定的執(zhí)行主類(含main方法那個(gè)類)在虛擬機(jī)啟動(dòng)時(shí)會(huì)仙貝初始化。
除了上面這4種方式,所有引用類的方法都不會(huì)觸動(dòng)初始化,稱為被動(dòng)引用。上面這4種方式是主動(dòng)引用。如:通過(guò)子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類初始化;通過(guò)數(shù)組定義來(lái)引用類,不會(huì)觸發(fā)此類的初始化;引用類的靜態(tài)扁郎不會(huì)觸發(fā)定義常量類的初始化,因?yàn)槌A吭诰幾g階段已經(jīng)被放入到常量池中了。
(五)、小結(jié):
上面就是一個(gè)類加載兵可以使用的整個(gè)過(guò)程,Java的類加載這種只有需要的時(shí)候才加載進(jìn)來(lái)的做法為內(nèi)存節(jié)省了很大的空間。
總體流程如下:
四、"類"結(jié)束生命周期
在以下情況的時(shí)候,Java虛擬會(huì)結(jié)束生命周期
- 1、執(zhí)行了System.exit()方法
- 2、程序正常執(zhí)行結(jié)束
- 3、程序在執(zhí)行過(guò)程中遇到了異常或錯(cuò)誤而異常終止
- 4、由于操作系統(tǒng)出現(xiàn)錯(cuò)誤而導(dǎo)致Java虛擬機(jī)進(jìn)程終止
五、new 一個(gè)對(duì)象過(guò)程詳解
上面的基本上都是理論知識(shí),但是怎么把理論知識(shí)轉(zhuǎn)化為實(shí)戰(zhàn)經(jīng)驗(yàn),是需要大家平時(shí)工作中日益積累的。所以很多招聘者會(huì)在面試中增加一個(gè)問(wèn)題——“請(qǐng)描述Java中 new一個(gè)對(duì)象的過(guò)程”類似的面試題,這道題其實(shí)說(shuō)深也深,說(shuō)淺也淺。那我們就結(jié)合我們上面的理論知識(shí),來(lái)是描述一下"Java中 new一個(gè)對(duì)象的過(guò)程"
我們將整個(gè)過(guò)程劃分為兩個(gè)部分:
- 1 類加載過(guò)程
- 2 對(duì)象創(chuàng)建過(guò)程
下面我們就詳細(xì)跟蹤下:
- 1、類加載過(guò)程
- 1.1、JVM會(huì)先去方法區(qū)中找到?jīng)]有相應(yīng)類的.class存在。如果有,就直接使用;如果沒(méi)有就把相關(guān)的.class加載到方法區(qū)。
- 1.2、在.class加載到方法區(qū)時(shí),會(huì)分為兩個(gè)部分加載:先加載非靜態(tài)內(nèi)容,再加載靜態(tài)內(nèi)容。
- 1.3、加載非靜態(tài)內(nèi)容:把.class中的所有非靜態(tài)內(nèi)容加載到方法區(qū)下的非靜態(tài)區(qū)域內(nèi)
- 1.4、加載靜態(tài)內(nèi)容:
- 1.4.1、把.class中的靜態(tài)內(nèi)容加載到方法區(qū)下的靜態(tài)區(qū)域內(nèi)
- 1.4.2、靜態(tài)內(nèi)容加載完成之后,對(duì)所有靜態(tài)變量進(jìn)行默認(rèn)初始化
- 1.4.3、所有靜態(tài)變量默認(rèn)初始化完成之后,再進(jìn)行顯示初始化
- 1.4.4、當(dāng)靜態(tài)區(qū)域下的所有靜態(tài)變量顯示初始化后,執(zhí)行靜態(tài)代碼塊
- 1.5、當(dāng)靜態(tài)區(qū)域下的靜態(tài)代碼塊,執(zhí)行完之后,整個(gè)類的加載就完成了。
- 2、對(duì)象創(chuàng)建過(guò)程
- 2.1、在堆內(nèi)存中開(kāi)辟一塊空間
- 2.2、給開(kāi)辟空間分配一個(gè)地址
- 2.3、把對(duì)象的所有非靜態(tài)成員加載到所開(kāi)辟的空間下
- 2.4、所有非靜態(tài)成員變量默認(rèn)初始化完成之后,調(diào)用構(gòu)造函數(shù)
- 2.5、所有非靜態(tài)變量默認(rèn)初始化完成之后,調(diào)用構(gòu)造函數(shù)
- 2.6、在構(gòu)造函數(shù)入棧時(shí),分為兩部分:先執(zhí)行構(gòu)造函數(shù)中的隱式三式,再執(zhí)行構(gòu)造函數(shù)中書(shū)寫(xiě)的代碼
- 2.6.1、隱式三步
- 2.6.1、執(zhí)行super語(yǔ)句
- 2.6.2、對(duì)開(kāi)辟空間下的所有非靜態(tài)成員變量進(jìn)行顯式初始化
- 2.6.3、執(zhí)行構(gòu)造代碼塊
- 2.6.2、在隱式三步執(zhí)行完之后,執(zhí)行構(gòu)造函數(shù)中書(shū)寫(xiě)的代碼
- 2.7、在整個(gè)構(gòu)造函數(shù)執(zhí)行完并彈棧后,把空間分配的地址賦值給一個(gè)引用對(duì)象
五、總結(jié)
我們先來(lái)看下Java程序的執(zhí)行流程圖:
再來(lái)看下JVM的大致物理結(jié)構(gòu)圖
Java虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存中,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型,這就是虛擬機(jī)的加載機(jī)制。Class文件由該類裝載器裝載后,在JVM中將形成一份描述Class結(jié)構(gòu)的元信息對(duì)象,通過(guò)該元信息對(duì)象可以獲知Class的結(jié)構(gòu)信息:如構(gòu)造函數(shù),屬性和方法等,Java允許用戶借用這個(gè)Class相關(guān)的元信息對(duì)象簡(jiǎn)介調(diào)用Class對(duì)象的功能,這里就是我們經(jīng)常能見(jiàn)到的Class類。
類從被加載到虛擬機(jī)存在開(kāi)始,到卸載出內(nèi)存位置,它的整個(gè)生命周期包括了:"加載(Loading)"、"驗(yàn)證(Verification)"、"準(zhǔn)備(Preparation)"、"解析(Resolution)"、"初始化(Initialization)"、"使用(using)"和"卸載(Unloading)"七個(gè)階段。其中驗(yàn)證、準(zhǔn)備和解析三個(gè)部分統(tǒng)稱為"連接(Linking)",這七個(gè)階段的發(fā)生順序如下:
大家喜歡就點(diǎn)贊,您的每一次點(diǎn)贊,都是我努力和進(jìn)步的動(dòng)力!您可能想不到:您的小小一按,可能就會(huì)對(duì)另外一個(gè)人產(chǎn)生翻天覆地的影響。!最后謝謝您的支持與厚愛(ài)