類加載機(jī)制
把class文件加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行校驗(yàn)致扯,準(zhǔn)備肤寝,解析,初始化抖僵,形成可以被虛擬機(jī)直接使用的字節(jié)碼
類加載的時(shí)機(jī)(觸發(fā)類的初始化)
- 使用new關(guān)鍵字實(shí)例化對象
- 讀取一個(gè)類的靜態(tài)代碼塊
- 使用java.lang.reflect包的方式對類進(jìn)行反射調(diào)用
類加載過程
整個(gè)生命周期包括:加載鲤看、校驗(yàn)、準(zhǔn)備耍群、解析义桂、初始化找筝、使用和卸載7個(gè)階段。
- 加載:通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流慷吊,將這個(gè)字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)呻征,在內(nèi)存中生成一個(gè)代表這個(gè)類的Class對象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口
- 校驗(yàn):校驗(yàn)是連接階段的第一步罢浇,這一階段的目的是確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求陆赋,并且不會危害虛擬自身的安全。
- 準(zhǔn)備:準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段嚷闭,這些變量所使用的內(nèi)存都將在方法去中進(jìn)行分配攒岛。這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量(static),而不包括實(shí)例變量胞锰,實(shí)例變量將會在對象實(shí)例化時(shí)隨著對象一起分配在Java堆中灾锯。
- 解析:解析階段是虛擬機(jī)將常量池內(nèi)的符號(Class文件內(nèi)的符號)引用替換為直接引用(指針)的過程。
- 初始化:初始化階段是類加載過程的最后一步嗅榕,開始執(zhí)行類中定義的Java程序代碼(字節(jié)碼)顺饮。
加載
- 通過全類名獲取class文件的二進(jìn)制字節(jié)流
- 將字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
- 生成一個(gè)代表該類的class對象,作為方法區(qū)這些數(shù)據(jù)的訪問入口
根據(jù)字節(jié)碼在java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對象
校驗(yàn)
- 驗(yàn)證Class文件中二進(jìn)制字節(jié)流符合虛擬機(jī)的要求凌那,不會涉及到虛擬機(jī)的安全
- 文件格式驗(yàn)證
- 元數(shù)據(jù)驗(yàn)證
- 字節(jié)碼驗(yàn)證
準(zhǔn)備
- 為類變量分配內(nèi)存空間和設(shè)置初始值的階段
- 為新生對象分配內(nèi)存
為新生對象分配內(nèi)存
- 如果Java堆內(nèi)存是規(guī)整連續(xù)的兼雄,采用“指針碰撞”的分配方式
- 如果不是連續(xù)規(guī)整的,采用“空閑列表”分配方式
內(nèi)存是否規(guī)整取決于垃圾收集器是否帶有壓縮整理功能
這個(gè)初始值和初始化階段的賦值不同帽蝶,這里指的是變量的默認(rèn)初始值赦肋,如果是final修飾的變量,就是賦予代碼里制定的初始值
解析
虛擬機(jī)將符號引用替換為直接引用的過程
符號引用 :符號引用以一組符號來描述所引用的目標(biāo)励稳。符號引用可以是任何形式的字面量佃乘,只要使用時(shí)能無歧義地定位到目標(biāo)即可,符號引用和虛擬機(jī)的布局無關(guān)驹尼。個(gè)人理解為:在編譯的時(shí)候一個(gè)每個(gè)java類都會被編譯成一個(gè)class文件趣避,但在編譯的時(shí)候虛擬機(jī)并不知道所引用類的地址,多以就用符號引用來代替新翎,而在這個(gè)解析階段就是為了把這個(gè)符號引用轉(zhuǎn)化成為真正的地址的階段程帕。
直接引用 :直接引用和虛擬機(jī)的布局是相關(guān)的,不同的虛擬機(jī)對于相同的符號引用所翻譯出來的直接引用一般是不同的料祠。如果有了直接引用骆捧,那么直接引用的目標(biāo)一定被加載到了內(nèi)存中澎羞。
初始化
根據(jù)程序代碼去初始化變量和其他資源髓绽,構(gòu)造函數(shù)
類加載器
BootstrapLoader/負(fù)責(zé)加載系統(tǒng)類
注意一個(gè)很重要的問題,就是java在邏輯上并不存在BootstrapKloader的實(shí)體妆绞,因?yàn)樗莄++編寫的顺呕,所以打印其內(nèi)容會是null
啟動類加載器主要加載 jre/lib下的jar文件枫攀。
ExtClassLoader/負(fù)責(zé)加載擴(kuò)展類//繼承類和實(shí)現(xiàn)類
擴(kuò)展類加載器主要加載 jre/lib/ext 下的jar文件。
AppClassLoader/負(fù)責(zé)加載應(yīng)用類
應(yīng)用程序類加載器主要加載 classpath 下的文件
Android類加載器
對于Android而言株茶,最終的apk文件包含的是dex類型的文件来涨,dex文件是將class文件重新打包,打包的規(guī)則又不是簡單地壓縮启盛,而是完全對class文件內(nèi)部的各種函數(shù)表蹦掐,變量表進(jìn)行優(yōu)化,產(chǎn)生一個(gè)新的文件僵闯,即dex文件卧抗。因此加載這種特殊的Class文件就需要特殊的類加載器DexClassLoader。
雙親委派模型
當(dāng)加載一個(gè)類時(shí)鳖粟,會優(yōu)先使用父類加載器加載社裆,當(dāng)父類加載器無法加載時(shí)才會使用子類加載器去加載。這么做的目的是為了避免類的重復(fù)加載
解釋器
對字節(jié)碼逐條解釋執(zhí)行向图,這種方式的執(zhí)行速度相對會比較慢泳秀;重復(fù)執(zhí)行需要重復(fù)解釋
JIT(即時(shí)編譯器)
在運(yùn)行時(shí),虛擬機(jī)將會把這些頻繁調(diào)用的代碼編譯成與本地平臺相關(guān)的機(jī)器碼榄攀,并進(jìn)行優(yōu)化嗜傅,可重復(fù)執(zhí)行,緩存效率高
Class文件字節(jié)碼結(jié)構(gòu)
魔數(shù)—副版本號—主版本號—常量池計(jì)數(shù)器—常量池?cái)?shù)據(jù)區(qū)—訪問標(biāo)志—類索引—父類索引—接口計(jì)數(shù)器—接口信息數(shù)據(jù)區(qū)—字段計(jì)數(shù)器—字段信息數(shù)據(jù)區(qū)—方法計(jì)數(shù)器—方法信息數(shù)據(jù)區(qū)—屬性計(jì)數(shù)器—屬性信息數(shù)據(jù)區(qū)
魔數(shù):class文件的標(biāo)志檩赢,確定這個(gè)文件是否為一個(gè)能被虛擬機(jī)接收的class文件
類B繼承A磺陡,A、B兩個(gè)類中都有靜態(tài)變量漠畜、成員變量币他、靜態(tài)代碼塊、構(gòu)造方法執(zhí)行順序是什么憔狞?
1.父類【靜態(tài)成員】和【靜態(tài)代碼塊】蝴悉,按在代碼中出現(xiàn)的順序依次執(zhí)行。
2.子類【靜態(tài)成員】和【靜態(tài)代碼塊】瘾敢,按在代碼中出現(xiàn)的順序依次執(zhí)行拍冠。
3.父類的【普通成員變量被普通成員方法賦值】和【普通代碼塊】,按在代碼中出現(xiàn)的順序依次執(zhí)行簇抵。
4.執(zhí)行父類的構(gòu)造方法庆杜。
5.子類的【普通成員變量被普通成員方法賦值】和【普通代碼塊】,按在代碼中出現(xiàn)的順序依次執(zhí)行碟摆。
6.執(zhí)行子類的構(gòu)造方法晃财。
JVM在搜索類的時(shí)候,又是如何判定兩個(gè)class是相同的呢典蜕?
JVM在判定兩個(gè)class是否相同時(shí)断盛,不僅要判斷兩個(gè)類名是否相同罗洗,而且要判斷是否由同一個(gè)類加載器實(shí)例加載的。只有兩者同時(shí)滿足的情況下钢猛,JVM才認(rèn)為這兩個(gè)class是相同的伙菜。
就算兩個(gè)class是同一份class字節(jié)碼,如果被兩個(gè)不同的ClassLoader實(shí)例所加載命迈,JVM也會認(rèn)為它們是兩個(gè)不同class贩绕。
類的加載過程,Person person = new Person();為例進(jìn)行說明壶愤。
1).因?yàn)閚ew用到了Person.class丧叽,所以會先找到Person.class文件,并加載到內(nèi)存中;
2).執(zhí)行該類中的static代碼塊公你,如果有的話踊淳,給Person.class類進(jìn)行初始化;
3).在堆內(nèi)存中開辟空間分配內(nèi)存地址;
4).在堆內(nèi)存中建立對象的特有屬性,并進(jìn)行默認(rèn)初始化;
5).對屬性進(jìn)行顯示初始化;
6).對對象進(jìn)行構(gòu)造代碼塊初始化;
7).對對象進(jìn)行與之對應(yīng)的構(gòu)造函數(shù)進(jìn)行初始化;
8).將內(nèi)存地址付給棧內(nèi)存中的p變量
class實(shí)例和class對象的區(qū)別
java有兩種對象:實(shí)例對象和Class對象陕靠。
每個(gè)類的運(yùn)行時(shí)的類型信息就是用Class對象表示的迂尝。它包含了與類有關(guān)的信息。
其實(shí)我們的實(shí)例對象就通過Class對象來創(chuàng)建的
有三種獲得Class對象的方式:
- Class.forName(“類的全限定名”)
- 實(shí)例對象.getClass()
- 類名.class (類字面常量)
實(shí)例newInstance()
最后
看完有什么不懂的可以在評論區(qū)問我剪芥,覺得對你有幫助的話記得給我點(diǎn)個(gè)贊垄开!