從HotSpot虛擬機(jī)源碼了解Java的訪問控制修飾符
前面Ribbon源碼分析文章,有讀者留言提問:XX類是包私有的,重寫不會(huì)報(bào)錯(cuò)嗎?答案其實(shí)是XX類并非包私有,而是一個(gè)protected的靜態(tài)內(nèi)部類,所以重寫不會(huì)報(bào)錯(cuò)。
關(guān)于Java訪問控制修飾符的作用,筆者在初學(xué)Java時(shí)也是靠記,寫多了代碼自然也就能理解,但筆者很好奇底層的實(shí)現(xiàn),所以也嘗試從HotSpot虛擬機(jī)源碼尋找答案,解答我多年來(lái)的疑惑。
類、字段、方法都有哪些訪問控制修飾符?
私有<private>、子類可訪問<protected>、公開public、包私有<package>,默認(rèn)不加訪問控制修飾符就是包私有。
訪問范圍 | private | package | protected | public |
同一個(gè)類 | 可訪問 | 可訪問 | 可訪問 | 可訪問 |
同一包中的其他類 | 不可訪問 | 可訪問 | 可訪問 | 可訪問 |
不同包中的子類 | 不可訪問 | 不可訪問 | 可訪問 | 可訪問 |
不同包中的非子類 | 不可訪問 | 不可訪問 | 不可訪問 | 可訪問 |
包私有<package>指的是只有同一個(gè)包下的類可訪問,其它包下的類不可訪問。
今天我們就深入java虛擬機(jī)去探究這些訪問控制修飾符語(yǔ)意的實(shí)現(xiàn)。
InstanceKlass是HotSpot VM中對(duì)應(yīng)class文件結(jié)構(gòu)的數(shù)據(jù)結(jié)構(gòu),InstanceKlass對(duì)象是一個(gè)Java類被HotSpot VM加載后所生成的C++對(duì)象,被存于方法區(qū)。我們?cè)贘ava代碼中使用的Class對(duì)象實(shí)際是InstanceKlass的一個(gè)鏡像。
Java支持使用"this."、"suppor."、"某個(gè)對(duì)象."調(diào)用一個(gè)方法,或"某個(gè)類."調(diào)用靜態(tài)方法,在我們看來(lái)是調(diào)用某個(gè)類的靜態(tài)方法或者對(duì)象的方法,但這在虛擬機(jī)中并不存在區(qū)別,都是一個(gè)方法調(diào)用。
調(diào)用靜態(tài)方法和對(duì)象方法的區(qū)別只在于,調(diào)用對(duì)象的方法需要在方法參數(shù)傳遞一個(gè)"this"引用,這是一個(gè)隱式參數(shù),在編譯器將Java代碼編譯成字節(jié)碼時(shí)自動(dòng)添加上。
而Java代碼中使用"this."、"suppor."調(diào)用自身方法和父類方法的不同,僅僅只是生成方法調(diào)用字節(jié)碼指令的操作數(shù)指向的Methodref常量不同,方法的第一個(gè)隱式參數(shù)傳遞的對(duì)象都是同一個(gè)。Methodref常量指代一個(gè)方法的符號(hào)引用,包括類名、方法名、方法描述符。
我們知道,類加載過(guò)程包括加載、鏈接、初始化三個(gè)階段,其中鏈接階段又可細(xì)分為驗(yàn)證、準(zhǔn)備和解析三個(gè)階段。下面這張圖有助于我們理解類加載的幾個(gè)階段,但并不準(zhǔn)確。
《Java虛擬機(jī)規(guī)范》只是規(guī)定類加載需要完成的事情,而對(duì)順序并沒有嚴(yán)格的要求。
下圖為筆者閱讀HotSpot虛擬機(jī)類加載源碼總結(jié)出的一張流程圖,僅供參考。(如需要獲取原圖,可在公眾號(hào)回復(fù):"hotspot")
在HotSpot虛擬機(jī)中,鏈接階段的準(zhǔn)備階段在加載階段之后完成,鏈接階段的驗(yàn)證也分多種驗(yàn)證,其中文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證在加載階段交叉完成,而字節(jié)碼驗(yàn)證階段則在類初始化之前才觸發(fā),解析階段則在類加載完成之后。
引起類初始化的幾條指令如new、getstatic、putstatic、invokestatic,虛擬機(jī)在執(zhí)行這些指令時(shí),先判斷類是否已經(jīng)初始化,未初始化則完成類的初始化,鏈接階段會(huì)在類初始化階之前觸發(fā)。
鏈接階段的解析階段是Java虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程,根據(jù)《Java虛擬機(jī)規(guī)范》規(guī)定,在ane-warray、checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invoke-special、invokestatic、invokevirtual、ldc、mulianewarray、new、putfield、putstatic這些要求操作數(shù)指向常量池中的符號(hào)引用常量(如:CONSTANT_Class_info、CONSTANT_Field_info、CONSTANT_Methodref_info)的指令執(zhí)行之前,必須先對(duì)使用的符號(hào)引用進(jìn)行解析。
符號(hào)引用以一組符號(hào)描述引用的目標(biāo),如CONSTANT_Class_info表示引用的類、CONSTANT_Field_info表示引用哪個(gè)類的哪個(gè)字段、CONSTANT_Methodref_info表示引用哪個(gè)類的哪個(gè)方法。
符號(hào)引用驗(yàn)證發(fā)生在解析階段,符號(hào)引用驗(yàn)證包括:通過(guò)字符串描述的全限定名是否能找到對(duì)應(yīng)的類、在指定的類中是否存在簡(jiǎn)單名稱所描述的方法和字段、符號(hào)引用中的類、字段、方法的可訪問性(
在HotSpot虛擬機(jī)的實(shí)現(xiàn)中,對(duì)于解釋執(zhí)行與動(dòng)態(tài)調(diào)用(invokedynamic),解析階段是在符號(hào)引用將要被使用前才去解析。
方法調(diào)用源碼:javaCalls.cpp; 鏈接解析源碼:linkResolver.cpp;
- // 檢查類
- LinkResolver::check_klass_accessability
- // 檢查方法
- LinkResolver::check_method_accessability
- // 檢查字段
- LinkResolver::check_field_accessability
這些方法調(diào)用最后都調(diào)用Reflection類的對(duì)應(yīng)verify方法完成是否可訪問的判斷,例如Reflection::verify_field_access方法。
Java虛擬機(jī)在解析class文件結(jié)構(gòu)時(shí)、在字節(jié)碼驗(yàn)證階段,也會(huì)對(duì)訪問控制修飾符進(jìn)行驗(yàn)證。
例如,在解析class文件結(jié)構(gòu)時(shí),驗(yàn)證是否能夠繼承父類(Reflection::verify_class_access):
類的訪問修飾符決定了一個(gè)類是否可以被其它類訪問。在解析class文件結(jié)構(gòu)階段,虛擬機(jī)可以驗(yàn)證當(dāng)前類是否能夠繼承父類(父類的訪問控制修飾符決定)、是否能夠?qū)崿F(xiàn)每個(gè)接口(接口的訪問修飾符決定)。
在字節(jié)碼驗(yàn)證階段則驗(yàn)證當(dāng)前類是否可以訪問目標(biāo)類的protected修飾的方法或字段:
在字節(jié)碼驗(yàn)證階段,虛擬機(jī)會(huì)對(duì)類的每個(gè)方法中的每條字節(jié)碼指令都會(huì)進(jìn)行驗(yàn)證,但虛擬機(jī)在字節(jié)碼驗(yàn)證階段,只對(duì)getfield指令做了check_protected驗(yàn)證。可見,字節(jié)碼驗(yàn)證階段沒有做過(guò)多的訪問控制驗(yàn)證。
本文轉(zhuǎn)載自微信公眾號(hào)「 Java藝術(shù)」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 Java藝術(shù)公眾號(hào)。