jvm字節(jié)碼
jvm字節(jié)碼一般結(jié)構(gòu)
使用javap -verbose 命令分析一個類,得到的是一個類字節(jié)碼的魔數(shù),版本號,常量池,類信息,類的構(gòu)造方法,類的方法信息,類變量與成員變量等信息。當(dāng)然也可以配合jclasslib這個idea插件來學(xué)習(xí)字節(jié)碼內(nèi)容
- 魔數(shù):.class字節(jié)碼的前四個字節(jié)即為魔數(shù)。魔數(shù)為固定值:0xCAFEBABE
- 版本號:魔數(shù)之后的四個字節(jié)即為版本號,前兩個字節(jié)為 minor version 次版本號,后兩個字節(jié)為 major version 主版本號。
- 常量池:主版本號之后的即為常量池入口,常量池是可變的,不確定的??梢詫⒊A砍乜醋鲱愇募馁Y源倉庫。常量池中主要存儲兩類常量:字面常量和符號引用。字面常量如文本字符串。符號引用:類和接口的全局限定名,字段的名稱和描述符,方法的名稱和描述符。
常量池由常量池數(shù)量和常量池表構(gòu)成,- 常量池數(shù)量
常量池數(shù)量在主版本號之后,占據(jù)兩個字節(jié)。 - 常量池表
在常量池數(shù)量之后。常量池數(shù)組中不同元素的類型結(jié)構(gòu)是不同的,因此長度不同。但是每一種元素的第一個數(shù)據(jù)都是一個u1類型,該位置是標志位,占用一個字節(jié),jvm在解析常量池時,會根據(jù)u1類型來獲取元素的具體類型。
- 常量池數(shù)量
- 類信息:
- 類的訪問修飾符(u2)
- 類的名字(常量池索引)
- 父類名字(常量池索引)
- 接口個數(shù)
- 接口表
- 成員變量:
- 成員變量數(shù)(u2)
- 成員變量表(包含類變量和和實例變量)
- 權(quán)限描述符
- 名字索引
- 類型描述符索引
- 屬性數(shù)
- 屬性信息
- 方法
- 方法個數(shù)(u2)
- 方法表
- 權(quán)限描述符(u2)
- 名字索引(u2)
- 類型描述符索引(u2)
- 屬性數(shù)(u2)
- 屬性信息
屬性信息結(jié)構(gòu)- 屬性類型索引(u2)
- 屬性信息長度(u4),本方法屬性之后的信息長度
- 最大棧深(u2)
- 最大局部變量數(shù)(u2),包含方法的參數(shù)和this。
- 操作碼表長度(u4)
- 操作碼表(u2,java虛擬機指令集)
- 異常表長度
- 異常表
- 作用的開始行
- 作用的結(jié)束行
- 異常出現(xiàn)跳轉(zhuǎn)行
- 異常名字索引
- 屬性表長度(u2)
- 屬性表:屬性表是code的屬性,如linenumbertype,存儲源代碼和字節(jié)碼行號對應(yīng)關(guān)系。localvariabletable,存儲方法中變量的類型,名字和描述。
- 屬性名索引(u2)
- 屬性長度(u4)
- 屬性數(shù)(u2):表示有幾個屬性
linenumbertype屬性內(nèi)容結(jié)構(gòu):
{
對應(yīng)關(guān)系:u4類型,有多個,(u2-u2)表示字節(jié)碼中行號和源代碼行號的對應(yīng)關(guān)系
}
localvariabletable屬性內(nèi)容結(jié)構(gòu):
{
局部變量在字節(jié)碼中開始作用的位置:u2類型
局部變量在字節(jié)碼中作用的長度:u2類型
局部變量名稱索引:u2類型
局部變量描述符索引:u2類型
局部變量localvariabletable中的索引
}
- 屬性
- 屬性數(shù)量(u2)
- 屬性表
- 屬性名索引(u2)
- 屬性值
屬性值長度(u4)
屬性值索引(u2)
jvm class文件是線性的,但可以嘗試使用json理解字節(jié)碼的結(jié)構(gòu),以下為json結(jié)構(gòu)。
開頭表示線性排序表示*
屬性帶" x"表示可以有多個
{
"*Magic Number":"0xCAFEBABE",
"Version":{
"*minor version":"u2",
"*major version":"u2"
},
"Constant Pool":{
"*Constant_count":"u2",
"*Constant_info":[
"類型眾多,參照表格,基本結(jié)構(gòu)為u1+n個字節(jié)。"
]
},
"Class info":{
"*Access Flags":"u2,類的訪問修飾符",
"*This Class Name":"u2類名",
"*Super Class Name":"u2 父類名字",
"*Interfaces_count":"接口數(shù)",
"*Interfaces_name":["接口名有多個"]
},
"Fields":{
"*Fields_count":"u2,成員變量數(shù)",
"Fields_info *x":[
{
"*access_flags":"u2,權(quán)限修飾符",
"*name_index":"u2,變量名稱索引" ,
"*descriptor_index":"u2,類型描述符索引",
"*attribute_count":"u2,屬性數(shù)目",
"attribute_info *x":[
{}
]
}
]
},
"Methods":{
"*methods_count":"u2,方法數(shù)",
"methods_info *x":[
{
"*access_flags":"u2,訪問修飾符",
"*name_index":"u2,方法名稱索引",
"*descriptor_index":"u2,類型描述符索引",
"*attribute_count":"u2,屬性數(shù)目",
"attribute_info *x":[
{
"*attribute_name_index":"u2,屬性名索引",
"*attribute_length":"u4,屬性長度,指之后數(shù)據(jù)長度",
"*max_stack":"u2,最大棧深",
"*max_locals":"u2,最大局部變量數(shù)",
"*code_length":"u4,操作碼長度",
"code":[
"*code1",
"*code2",
"*...code為u1組成"
],
"*exception_table_length":"u2,異常表長度",
"exception_table":[
{
"*Start_Pc":"u4,異常開始作用行",
"*Eed_Pc":"u4,異常結(jié)束作用行",
"*Handler_Pc":"u4,異常發(fā)生跳轉(zhuǎn)行",
"*Exception_name_index":"u2,異常名索引"
}
],
"*attribute_count":"u2,屬性數(shù)目",
"attribute_info *x":[
{
"*attribute_name_index":"u2,屬性名索引",
"*attribute_length":"u4,屬性長度,指之后數(shù)據(jù)長度",
"*attribute_count":"表示有幾個對應(yīng)關(guān)系",
"attribute_info":[
{
"*linenumbertype":"字節(jié)碼和源碼行號對照表,u4類型,有多個,(u2-u2)表示字節(jié)碼中行號和源代碼行號的對應(yīng)關(guān)系"
},
{
"localvariabletable":"局部變量表",
"*局部變量在字節(jié)碼中開始作用的位置":"u2類型",
"*局部變量在字節(jié)碼中作用的長度":"u2類型",
"*局部變量名稱索引":"u2類型",
"*局部變量描述符索引":"u2類型",
"*局部變量localvariabletable中的索引":"u2"
}
]
}
]
}
]
}
]
},
"Attributes":{
"*attribute_count":"u2,屬性數(shù)目",
"attribute_table":[
{
"*attribute_name_index":"u2,屬性名索引",
"*attribute_length":"屬性值長度",
"*attribute_index":"屬性值索引"
}
]
}
}
synchronized 關(guān)鍵字
- synchronized方法 在方法上加標識符
- synchronized靜態(tài)方法 在方法上加標識符
- synchronized代碼塊 編譯出不同兩個指令集,在中間的表示為同步的代碼塊。
靜態(tài)變量和一般變量
- 靜態(tài)變量和靜態(tài)代碼塊的指令集都在clinit()方法中執(zhí)行
- 實例變量的值和代碼塊都在init() 方法中執(zhí)行
字節(jié)碼中的異常
- jdk統(tǒng)一采用異常表的方式處理異常
- 當(dāng)異常處理存在finally語句塊時,現(xiàn)代化的jvm采取的方式是將finally語句塊的字節(jié)碼拼接到每一個catch語句塊后面,有多超catch語句塊,就拼接多少次。
- 方法后throws的異常會在method_info下生成一個異常數(shù)組
棧幀和操作數(shù)棧,直接引用和間接引用
- 棧幀是一種幫助虛擬機執(zhí)行方法調(diào)用與方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu)。
- 棧幀本身是一種數(shù)據(jù)結(jié)構(gòu),封裝了方法的局部變量表,動態(tài)鏈接信息,方法的返回值和操作數(shù)棧等信息。
- 有些符號引用在類加載階段或第一次使用時就轉(zhuǎn)換為直接引用,另一些符號引用則是在每次運行期轉(zhuǎn)為直接引用。這種轉(zhuǎn)換叫做動態(tài)鏈接,在java中體現(xiàn)為多態(tài)性。
- 棧幀結(jié)構(gòu)
{
局部變量表
操作數(shù)棧
動態(tài)鏈接
返回地址
幀數(shù)據(jù)區(qū)
}
重寫和重載的不同
分派:確定執(zhí)行哪個方法的過程
靜態(tài)類型:引用類型,不會被改變、在編譯器可知。
動態(tài)類型:實例對象類型,會變化、在運行期才可知
靜態(tài)分派:調(diào)用重載的方法。調(diào)用同一個類的不同的方法,編譯后會指向不同的符號引用(方法的參數(shù)不同,確定不同方法),這是顯而易見的,從代碼就可以看出方法調(diào)用的不同。
動態(tài)分派:調(diào)用重寫的方法,若有兩個父類引用指向不同的兩個子類對象,編譯后指向相同的符號引用,具體執(zhí)行哪個實例方法是由jvm決定的。
-
invokevirtual:只要是調(diào)用public方法,編譯后都是invokevirtual指令操作,
- invokevirtual指令執(zhí)行的第一步 = 確定接受者的實際類型
- invokevirtual指令執(zhí)行的第二步 = 將常量池中類方法符號引用解析到不同的直接引用上
調(diào)用重載的方法也是invokevirtual指令,這是因為重載的方法也有可能會被重寫,所有的public方法都采用invokevirtual指令,也是為了更好的體現(xiàn)多態(tài),多態(tài)在jvm層面是由invokevirtual指令實現(xiàn)的
-
動態(tài)綁定具體的調(diào)用過程為:
- 首先會找到被調(diào)用方法所屬類的全限定名
- 在此類的方法表中尋找被調(diào)用方法,如果找到,會將方法表中此方法的索引項記錄到常量池中(這個過程叫常量池解析),如果沒有,編譯失敗。
- 根據(jù)具體實例化的對象找到方法區(qū)中此對象的方法表,再找到方法表中的被調(diào)用方法,最后通過直接地址找到字節(jié)碼所在的內(nèi)存空間。
棧指令集和寄存器指令集
- jvm是基于棧的指令集,基于棧的指令集主要的操作有入棧和出棧兩種
- 基于棧的指令集可以在不同平臺之間移植,基于寄存器的指令集是與硬件相關(guān)的。
- 基于棧的指令集的缺點在于完成相同操作,指令的數(shù)量多運行速度慢?;诩拇嫫鞯闹噶罴俣葎t要快得多。
動態(tài)代理
- 在java基礎(chǔ)中了解到了動態(tài)代理這種模式,在jvm層面,動態(tài)代理的意思是代理類的字節(jié)碼是在運行期由jvm生成。
- 生成的代理類里面,最終還是會調(diào)用自己寫的InvocationHandler實現(xiàn)類里面的invoke方法,