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