這是我們 Java 虛擬機(jī)系列文件的第五篇,連接模型
從程序員的角度來看,理解 Java 虛擬機(jī)體系結(jié)構(gòu)最重要的方面之一就是連接模型
1.解析和動(dòng)態(tài)擴(kuò)展
編譯一個(gè) Java 程序之后,會(huì)得到程序中每個(gè)類和接口的獨(dú)立 class 文件。它們是通過接口符號(hào)(harbor)相互聯(lián)系的,或者用 Java API 的 class 文件相聯(lián)
class 文件把它所有的引用符號(hào)都保存在常量池中。在程序運(yùn)行時(shí),如果某個(gè)特定的接口符號(hào)將要被使用,它通過解析,根據(jù)符號(hào)引用查到實(shí)體,再把符號(hào)引用替換成一個(gè)直接引用的過程。
因?yàn)樗械姆?hào)引用都保存在常量池中,這個(gè)過程也被稱為常量池解析。
解析分成早解析和遲解析
- 早解析:程序在它的 main() 方法尚未被調(diào)用時(shí)就已經(jīng)完全連接了
- 遲解析:JVM 在執(zhí)行程序的第一次用到這個(gè)符號(hào)引用的最后一刻才去解析
連接包括把符號(hào)引用替換成直接引用,還包括檢查正確性和權(quán)限。
檢查內(nèi)容包含:
- 那個(gè)類是否存在
- 該類是返回有權(quán)訪問那個(gè)類
- 那個(gè)類中是否存在名稱相符的字段
- 那個(gè)字段的類型和期望的類型是否相符
- 本類是否有權(quán)訪問那個(gè)字段
- 那個(gè)字段的確是一個(gè)類變量,而不是一個(gè)實(shí)例變量。
Java 虛擬機(jī)為每一個(gè)裝載的類和接口都保存一份獨(dú)立的常量池。
2.類加載器和雙親委托機(jī)制
看前面的文章
3.常量池的解析
被初始化為編譯時(shí)常量的靜態(tài) final 變量的引用,在編譯時(shí)被解析為常量值的一個(gè)本地拷貝
4.其他類型的解析
4.1 解析 CONSTANT_Class_info 入口

- 裝載類或任何超類
在第一步,虛擬機(jī)確定被引用的已經(jīng)被裝載進(jìn)當(dāng)前命名空間,并為該類標(biāo)記為初設(shè)裝載器。
如果當(dāng)前類加載器是啟動(dòng)類加載器,虛擬機(jī)根據(jù)不同的實(shí)現(xiàn),使用不同的方式加載。
如果是自定義類加載器,則通過 loadClass() 方法完成加載請(qǐng)求,把需要加載的類的全限定名作為參數(shù)傳遞進(jìn)去。
- 裝載類或任何超類
被引用的類型被加載了,虛擬機(jī)檢測它的二進(jìn)制數(shù)據(jù)。如果類型是一個(gè)類,并且不是 java.lang.Object, 虛擬機(jī)根據(jù)類的數(shù)據(jù)得到它的直接超類的全限定名。如果超類沒有被加載進(jìn)當(dāng)前命名空間。如果沒有,先加載超類。一旦超類被加載了,虛擬在此檢查超類的二進(jìn)制數(shù)據(jù)來找到它的超類。一直重復(fù)到超類是 Object 為止。
一旦一個(gè)類型被加載進(jìn)當(dāng)前命名空間,而且通過遞歸,所有該類型的超類和超接口都會(huì)被加載成功。
2.檢查訪問權(quán)限
如果沒有訪問權(quán)限,會(huì)拋出 IllegalAccessError 異常,符號(hào)引用解析失敗。3.檢驗(yàn)類型
校驗(yàn)過程要去虛擬機(jī)加載的類型符合 Java 語言的語義4.準(zhǔn)備階段
準(zhǔn)備階段虛擬機(jī)為類變量以及實(shí)現(xiàn)不同有差別的數(shù)據(jù)結(jié)構(gòu)分配內(nèi)存5.解析類型
解析類型的階段是可以選擇的,根據(jù)參數(shù)選擇解析或不解析6.初始化類型
超類必須在子類之前被初始化,必須確保它的所有超類都被初始化,從 Object 開始沿著繼承的結(jié)構(gòu)向下處理,直達(dá)被引用的類。
如果一個(gè)類型沒有被連接,在初始化之前必須被連接。
超類必須被初始化,超接口是不必的
如果虛擬機(jī)因?yàn)閮?nèi)存不足,在初始化的時(shí)候會(huì)拋出 OutOfMemoryError 異常
4.2 解析 CONSTANT_Fieldref_info 入口
要解析 CONSTANT_Fieldref_info 入口,必須要先解析 CONSTANT_Class_info 入口。

搜索 Field 字段過程
- 1.虛擬機(jī)在被引用的類型中查找具有指定的名稱和類型的字段。如果找到,則成功。
- 2.否則虛擬機(jī)檢測類型的直接實(shí)現(xiàn)或拓展的接口,以及遞歸地檢查它們的超接口。如果找到,則成功
- 3.否則檢測直接超類,并且遞歸地檢查類型的所有超類。如果找到,則成功
- 4.最后都找不到,則字段搜索失敗
如果沒有在被引用的類或者它的任何超類中找到名字和類型都符合的字段,虛擬機(jī)就會(huì)拋出 NoSuchFieldError 異常
如果字段搜索成功,但是當(dāng)前類沒有權(quán)限訪問該字段,虛擬機(jī)就會(huì)拋出 IllegalAccessError 異常
成功了之后,虛擬機(jī)就會(huì)把這個(gè)入口標(biāo)記為已解析,并在這個(gè)常量池入口的數(shù)據(jù)中放上指向這個(gè)字段的直接引用。
4.3 解析 CONSTANT_Methodref_info 入口
要解析 CONSTANT_Methodref_info 入口,必須要先解析 CONSTANT_Class_info 入口。
解析 methodref 的過程
- 1.如果被解析的類型是一個(gè)接口,而非類,虛擬機(jī)就會(huì)拋出 IncompatibleClassChangeError 異常
- 2.如果解析的類型是一個(gè)類。虛擬機(jī)檢查被引用的類是否有一個(gè)符合指定名字以及描述符。如果找到,則成功
- 3.否則虛擬機(jī)檢查是否這個(gè)類直接實(shí)現(xiàn)了任何接口,并遞歸地檢查由類型直接實(shí)現(xiàn)的接口的超接口,查看是否有方法符合指定的名稱和描述符。如果找到,則成功
- 4.最后都找不到,則方法搜索失敗
如果沒有在被引用的類和它的任何超類中找到名稱、返回類型、參數(shù)數(shù)量和類型都符合的方法,虛擬機(jī)就會(huì)拋出 NoSuchMethodError 異常
成功了之后,虛擬機(jī)就會(huì)把這個(gè)入口標(biāo)記為已解析,并在這個(gè)常量池入口的數(shù)據(jù)中放上指向這個(gè)字段的直接引用。
4.4 解析 CONSTANT_InterfaceMethodref_info 入口
要解析 CONSTANT_InterfaceMethodref_info 入口,必須要先解析 CONSTANT_Class_info 入口。
解析 InterfaceMethodref 的過程
- 1.如果被解析的類型是一個(gè)類,而非接口,虛擬機(jī)就會(huì)拋出 IncompatibleClassChangeError 異常
- 2.如果解析的類型是一個(gè)接口。虛擬機(jī)檢查被引用的接口是否有符合指定名稱和描述符。如果找到,則成功
- 3.否則虛擬機(jī)檢查接口的直接超接口,并且遞歸地檢查接口的所有超接口,以及 java.lang.Object 類來查找符合指定名稱和描述符的方法。如果找到,則成功
- 4.最后都找不到,則接口方法搜索失敗
如果在被引用的接口和它的任何超類型中都招標(biāo)名稱、返回類型、參數(shù)數(shù)量和類型都符合的方法,虛擬機(jī)就會(huì)拋出 NoSuchMethodError 異常
成功了之后,虛擬機(jī)就會(huì)把這個(gè)入口標(biāo)記為已解析,并在這個(gè)常量池入口的數(shù)據(jù)中放上指向這個(gè)字段的直接引用。
4.5 解析 CONSTANT_String_info 入口
要解析 CONSTANT_String_info 入口,虛擬機(jī)必須把一個(gè)指定內(nèi)部字符串對(duì)象的引用放置到被解析的常量池入口數(shù)據(jù)中。該字符串對(duì)象必須按照 sting_index 項(xiàng)在 CONSTANT_String_info 中指明 CONTANT_Utf8_info 入口所指定的字符順序組織。
要完成 CONSTANT_String_info 入口的解析過程,虛擬機(jī)應(yīng)把指向被拘留的字符串對(duì)象的引用放置到被解析的常量表入口數(shù)據(jù)中。
5.直接引用
常量池解析的最終目標(biāo)是把符號(hào)引用替換為直接引用
直接存儲(chǔ)的格式是用指針
指向類、類變量和類方法的直接引用是指向方法區(qū)的本地指針
指向?qū)嵗兞亢蛯?shí)例方法的直接引用都是偏移變量。實(shí)例變量的直接引用是從對(duì)象的映像開始算起到這個(gè)實(shí)例位置的偏移量。實(shí)例方法的直接引用是方發(fā)表的偏移量。