Java字節(jié)碼

參考鏈接:一文讓你明白Java字節(jié)碼

??????????????????Java字節(jié)碼

??????????????????Java虛擬機(jī)字節(jié)碼指令

????????Java號(hào)稱是一門(mén)“一次編譯到處運(yùn)行”的語(yǔ)言,但是我們對(duì)這句話的理解深度又有多少呢?從我們寫(xiě)的java文件到通過(guò)編譯器編譯成java字節(jié)碼文件(也就是.class文件),這個(gè)過(guò)程是java編譯過(guò)程;而我們的java虛擬機(jī)執(zhí)行的就是字節(jié)碼文件。不論該字節(jié)碼文件來(lái)自何方,由哪種編譯器編譯,甚至是手寫(xiě)字節(jié)碼文件,只要符合java虛擬機(jī)的規(guī)范,那么它就能夠執(zhí)行該字節(jié)碼文件。

1、什么是字節(jié)碼

????????字節(jié)碼是Java程序的中間表示,就好比匯編是C或C++程序的中間表示。字節(jié)碼是Java文件通過(guò)javac編譯器產(chǎn)生的(也就是.class文件)。程序中的字節(jié)碼,不管是運(yùn)行時(shí)JIT還是HotSpot,字節(jié)碼都是你程序大小和執(zhí)行速度的重要的一部分。注意,你擁有的字節(jié)碼越多,.class文件就越大,JIT或HotSpot運(yùn)行時(shí)也就需要編譯更多的代碼。

2、解讀字節(jié)碼文件

java字節(jié)碼文件

????????用javac命令或者ide工具將該java源文件編譯成java字節(jié)碼文件,編譯好的字節(jié)碼文件,我們可以看到一堆16進(jìn)制的字節(jié)。如果使用IDE去打開(kāi),也許看到的是已經(jīng)被反編譯的我們所熟悉的java代碼,而這才是純正的字節(jié)碼,這也是本文的內(nèi)容重點(diǎn)。對(duì)這樣一堆字節(jié)碼感到頭疼,不過(guò)沒(méi)關(guān)系,慢慢試著看懂它。

java字節(jié)碼的總覽圖

????????我們也就是按照上面的順序來(lái)對(duì)字節(jié)碼進(jìn)行解讀的。一共含有10部分,包含魔數(shù),版本號(hào),常量池等等,接下來(lái)我們按照順序一步一步解讀。

2.1、魔數(shù)(Magic Number:4個(gè)字節(jié))

????????前4個(gè)字節(jié)表示的是魔數(shù),對(duì)應(yīng)Demo的是 0XCAFE BABE。魔數(shù)是用來(lái)區(qū)分文件類型的一種標(biāo)志,一般都是用文件的前幾個(gè)字節(jié)來(lái)表示。比如0XCAFE BABE表示的是class文件,那么有人會(huì)問(wèn),文件類型可以通過(guò)文件名后綴來(lái)判斷???是的,但是文件名是可以修改的(包括后綴),那么為了保證文件的安全性,將文件類型寫(xiě)在文件內(nèi)部來(lái)保證不被篡改。從java的字節(jié)碼文件類型我們看到,CAFE BABE翻譯過(guò)來(lái)是咖啡寶貝之意,CAFE BABE = 咖啡。

2.2、JDK版本號(hào)(Version:2 + 2個(gè)字節(jié))

????????識(shí)別了文件類型之后,接下來(lái)是JDK版本號(hào)。版本號(hào)含次版本號(hào)(2個(gè)字節(jié))和主次版本號(hào)(2個(gè)字節(jié))。對(duì)應(yīng)Demo的是0X0000 0033。其中前面的0000是次版本號(hào),后面的0033是主版本號(hào)。通過(guò)進(jìn)制轉(zhuǎn)換得到的是次版本號(hào)為0,主版本號(hào)為51。

????????從oracle官方網(wǎng)站我們能知道,51對(duì)應(yīng)的正式JDK1.7,而其次版本為0,所以該文件的版本為1.7.0。如果需要驗(yàn)證,可以在用java –version命令輸出版本號(hào),或者修改編譯目標(biāo)版本–target重新編譯,查看編譯后的字節(jié)碼文件版本號(hào)是否做了相應(yīng)的修改。

2.3、常量池(Constant Pool:2 + n個(gè)字節(jié))

????????常量池是Class文件中的資源倉(cāng)庫(kù),下面內(nèi)容中很多地方會(huì)涉及,如Class Name,Interfaces等。常量池中主要存儲(chǔ)2大類常量:字面量和符號(hào)引用。字面量如文本字符串,java中聲明為final的常量值等等,而符號(hào)引用如類和接口的全局限定名,字段的名稱和描述符,方法的名稱和描述符。

????????為什么需要類和接口的全局限定名呢?系統(tǒng)引用類或者接口的時(shí)候不是通過(guò)內(nèi)存地址進(jìn)行操作嗎?java虛擬機(jī)在沒(méi)有將類加載到內(nèi)存的時(shí)候根本都沒(méi)有分配內(nèi)存地址,也就不存在對(duì)內(nèi)存的操作,所以java虛擬機(jī)首先需要將類加載到虛擬機(jī)中,那么這個(gè)過(guò)程設(shè)計(jì)對(duì)類的定位(需要加載A包下的B類,不能加載到別的包下面的別的類中),所以需要通過(guò)全局限定名來(lái)判別唯一性。這就是為什么叫做全局,限定的意思,也就是唯一性。

常量池的項(xiàng)目類型表

????????JDK1.7之后又增加了CONSTANT_MethodHandle_info,CONSTANT_MethodType_info和CONSTANT_InvokeDynamic_info,這樣算起來(lái)一共是14種。

0×0015:由于常量池的數(shù)量不固定(2+n),所以需要在常量池的入口處放置一項(xiàng)u2(u2 – 2個(gè)字節(jié))類型的數(shù)據(jù)代表常量池?cái)?shù)量。因此該16進(jìn)制是21,表示有20項(xiàng)常量,索引范圍為1~20。明明是21,為何是20呢?因?yàn)镃lass文件格式規(guī)定,設(shè)計(jì)者就講第0項(xiàng)保留出來(lái)了,以備后患。接下來(lái)需要翻譯出20項(xiàng)常量。

Constant #1 (一共有20個(gè)常量,這是第一個(gè),以此類推…)

0x0a-:從常量類型表中我們發(fā)現(xiàn),第一個(gè)數(shù)據(jù)均是u1(u1 – 1個(gè)字節(jié))類型的tag,16進(jìn)制的0a是十進(jìn)制的10,對(duì)應(yīng)表中的CONSTANT_MethodRef_info。

0x-00 04-:CONSTANT_Class_info索引項(xiàng)#4

0x-00 11-:CONSTANT_NameAndType_info索引項(xiàng)#17

Constant #2

0x-09: CONSTANT_FieldRef_info

0×0003 :CONSTANT_Class_info索引項(xiàng)#3

0×0012:CONSTANT_NameAndType_info索引項(xiàng)#18

Constant #3

0×07-: CONSTANT_Class_info

0x-00 13-: 全局限定名常量索引為#19

Constant #4

0x-07 :CONSTANT_Class_info

0×0014:全局限定名常量索引為#20

Constant #5

0×01:CONSTANT_Utf-8_info

0x-00 01-:字符串長(zhǎng)度為1(選擇接下來(lái)的一個(gè)字節(jié)長(zhǎng)度轉(zhuǎn)義)

0x-61:”a”(十六進(jìn)制轉(zhuǎn)ASCII字符)

Constant #6

0×01:CONSTANT_Utf-8_info

0x-00 01:字符串長(zhǎng)度為1

0x-49:”I”

Constant #7

0×01:CONSTANT_Utf-8_info

0x-00 06:字符串長(zhǎng)度為6

0x-3c 696e 6974 3e-:”<init>”

Constant #8

0×01 :CONSTANT_UTF-8_info

0×0003:字符串長(zhǎng)度為3

0×2829 56:”()V”

Constant #9

0x-01:CONSTANT_Utf-8_info

0×0004:字符串長(zhǎng)度為4

0x436f 6465:”Code”

Constant #10

0×01:CONSTANT_Utf-8_info

0×00 0f:字符串長(zhǎng)度為15

0x4c 696e 654e 756d 6265 7254 6162 6c65:”LineNumberTable”

Constant #11

ox01: CONSTANT_Utf-8_info

0×00 12字符串長(zhǎng)度為18

0x-4c 6f63 616c 5661 7269 6162 6c65 5461 626c 65:”LocalVariableTable”

Constant #12

0×01:CONSTANT_Utf-8_info

0×0004 字符串長(zhǎng)度為4

0×7468 6973 :”this”

Constant #13

0×01:CONSTANT_Utf-8_info

0x0f:字符串長(zhǎng)度為15

0x4c 636f 6d2f 6465 6d6f 2f44 656d 6f3b:”Lcom/demo/Demo;”

Constant #14

0×01:CONSTANT_Utf-8_info

0×00 0a:字符串長(zhǎng)度為10

ox74 6573 744d 6574 686f 64:”testMethod”

Constant #15

0×01:CONSTANT_Utf-8_info

0x000a:字符串長(zhǎng)度為10

0x536f 7572 6365 4669 6c65 :”SourceFile”

Constant #16

0×01:CONSTANT_Utf-8_info

0×0009:字符串長(zhǎng)度為9

0x-44 656d 6f2e 6a61 7661 :”Demo.java”

Constant #17

0x0c :CONSTANT_NameAndType_info

0×0007:字段或者名字名稱常量項(xiàng)索引#7

0×0008:字段或者方法描述符常量索引#8

Constant #18

0x0c:CONSTANT_NameAndType_info

0×0005:字段或者名字名稱常量項(xiàng)索引#5

0×0006:字段或者方法描述符常量索引#6

Constant #19

0×01:CONSTANT_Utf-8_info

0×00 0d:字符串長(zhǎng)度為13

0×63 6f6d 2f64 656d 6f2f 4465 6d6f:”com/demo/Demo”

Constant #20

0×01:CONSTANT_Utf-8_info

0×00 10 :字符串長(zhǎng)度為16

0x6a 6176 612f 6c61 6e67 2f4f 626a 6563 74 :”java/lang/Object”

2.4、訪問(wèn)標(biāo)志(Access Flags:2個(gè)字節(jié))

????????訪問(wèn)標(biāo)志信息包括該Class文件是類還是接口,是否被定義成public,是否是abstract,如果是類,是否被聲明成final。通過(guò)上面的源代碼,我們知道該文件是類并且是public。

Access Flags
Access Flags

0x00 21:是0×0020和0×0001的并集。

2.5、類索引(This Class Name:2個(gè)字節(jié))

????????類索引用于確定類的全限定名

0×00 03:表示引用第3個(gè)常量(上文2.3中的第3個(gè)常量),同時(shí)第3個(gè)常量引用第19個(gè)常量(上文2.3中的第19個(gè)常量),查找得”com/demo/Demo”。

2.6、父類索引(Super Class Name:2個(gè)字節(jié))

0×00 04:表示引用第4個(gè)常量(上文2.3中的第4個(gè)常量),同時(shí)第4個(gè)常量引用第20個(gè)常量(上文2.3中的第20個(gè)常量),查找得”java/lang/Object”。

2.7、接口索引(Interfaces:2 + n個(gè)字節(jié))

????????這個(gè)接口有2+n個(gè)字節(jié),前兩個(gè)字節(jié)表示的是接口數(shù)量,后面跟著就是接口的表。字節(jié)碼為0000說(shuō)明這個(gè)類沒(méi)有任何接口。

2.8、字段表集合(Fields:2 + n個(gè)字節(jié))

????????字段表用于描述類和接口中聲明的變量,這里的字段包含了類級(jí)別變量以及實(shí)例變量,但是不包括方法內(nèi)部聲明的局部變量。

????????這個(gè)接口有2+n個(gè)字節(jié),前兩個(gè)字節(jié)表示的是聲明的變量數(shù)量,后面跟著就是聲明的變量。字節(jié)碼為0001說(shuō)明這個(gè)類有一個(gè)聲明的變量。

????????那么接下來(lái)我們要針對(duì)這樣的字段進(jìn)行解析。

字段表結(jié)構(gòu)

0×00 02:訪問(wèn)標(biāo)志為private(見(jiàn)2.4)

0×00 05:字段名稱索引為#5,對(duì)應(yīng)的是”a”

0x00 06:描述符索引為#6,對(duì)應(yīng)的是”I”

0x00 00:屬性表數(shù)量為0,因此沒(méi)有屬性表。(見(jiàn)2.9)

2.9、方法(Methods:2 + n個(gè)字節(jié))

0x00 02:表示有兩個(gè)方法

方法表結(jié)構(gòu)

第一個(gè)方法

0×00 01:訪問(wèn)標(biāo)志 ACC_PUBLIC,表明該方法是public。(見(jiàn)2.4)

0×00 07:方法名索引為#7,對(duì)應(yīng)的是””

0×00 08:方法描述符索引為#8,對(duì)應(yīng)的是”()V”

0×00 01:屬性表數(shù)量為1(一個(gè)屬性表)

屬性表結(jié)構(gòu)

????????一個(gè)u2的屬性名稱索引,一個(gè)u2的屬性長(zhǎng)度加上屬性長(zhǎng)度的info。

????????虛擬機(jī)規(guī)范預(yù)定義的屬性有很多,比如Code,LineNumberTable,LocalVariableTable,SourceFile等等,這個(gè)網(wǎng)上可以搜索到。

????????按照上面的表結(jié)構(gòu)解析得到下面信息:

0×00 09:名稱索引為#9(“Code”)。

0×00 0000 38:屬性長(zhǎng)度為56字節(jié)。

Code屬性表結(jié)構(gòu)

????????前面6個(gè)字節(jié)(名稱索引2字節(jié)+屬性長(zhǎng)度4字節(jié))已經(jīng)解析過(guò)了,所以接下來(lái)就是解析剩下的56-6=50字節(jié)即可。

0×00 02:max_stack=2

0×00 01:max_locals=1

0×00 0000 0a:code_length=10

0x2a b700 012a 04b5 0002 b1:這是code代碼,可以通過(guò)虛擬機(jī)字節(jié)碼指令進(jìn)行查找。

2a=aload_0(將第一個(gè)引用變量推送到棧頂)

b7=invokespecial(調(diào)用父類構(gòu)造方法)

00=什么都不做

01=將null推送到棧頂

2a=同上

04=iconst_1 將int型1推送到棧頂

b5=putfield 為指定的類的實(shí)例變量賦值

00= 同上

02=iconst_m1 將int型-1推送棧頂

b1=return 從當(dāng)前方法返回void

整理,去除無(wú)動(dòng)作指令得到下面

0 : aload_0

1 : invokespecial

4 : aload_0

5 : iconst_1

6 : putfield

9 : return

關(guān)于虛擬機(jī)字節(jié)碼指令這塊內(nèi)容,后期會(huì)繼續(xù)深入下去…… 目前只需要了解即可。接下來(lái)順著Code屬性表繼續(xù)解析下去:

0×00 00:exception_table_length=0

0×00 02:attributes_count=2(Code屬性表內(nèi)部還含有2個(gè)屬性表)

LineNumberTable屬性結(jié)構(gòu)

0×00 0a:第一個(gè)屬性表是”LineNumberTable”

0×00 0000 0a:“屬性長(zhǎng)度為10″

0×00 02:line_number_table_length=2

????????line_number_table是一個(gè)數(shù)量為line_number_table_length,類型為line_number_info的集合,line_number_info表包括了start_pc和line_number兩個(gè)u2類型的數(shù)據(jù)項(xiàng),前者是字節(jié)碼行號(hào),后者是Java源碼行號(hào)

0×00 00:start_pc=0

0×00 03:end_pc=3

0×00 04:start_pc=4

0×00 04:end_pc=4

LocalVariableTable屬性結(jié)構(gòu)

0×00 0b:第二個(gè)屬性表是”LocalVariableTable”

0×00 0000 0c:屬性長(zhǎng)度為12

0×00 01:local_variable_table_length=1

local_variable_info表結(jié)構(gòu)

然后按照l(shuí)ocal_variable_info表結(jié)構(gòu)進(jìn)行解析:

0×00 00:start_pc=0

0×00 0a:length=10

0x000c:name_index=”this”

0x000d:descriptor_index #13 (“Lcom/demo/Demo”)

0x0000:index=0

第二個(gè)方法

0×00 04:”protected”

0×00 0e:#14(”testMethod”)

0×00 08:“()V”

0×0001:屬性數(shù)量=1

0×0009:”Code”

0×0000 002b:屬性長(zhǎng)度為43

解析一個(gè)Code表

0000:max_stack =0

0001:max_local =1

0000 0001:code_length =1

0xb1:return(該方法返回void)

0×0000:異常表長(zhǎng)度=0

0×0002:屬性表長(zhǎng)度為2

//第一個(gè)屬性表

0x000a:#10,LineNumberTable

0×0000 0006:屬性長(zhǎng)度為6

0×0001:line_number_length = 1

0×0000:start_pc =0

0×0008:end_pc =8

//第二個(gè)屬性表

0x000b:#11 ,LocalVariableTable

0×0000 000c:屬性長(zhǎng)度為12

0×0001:local_variable_table_length =1

0×0000:start_pc = 0

0×0001:length = 1

0x000c:name_index =#12 “this”

0x000d:描述索引#13 “Lcom/demo/Demo;”

0x0000:index=0

2.10、屬性(Attribute:2 + n個(gè)字節(jié))

0×0001:同樣的,表示有1個(gè)Attributes了。

0x000f:#15(“SourceFile”)

0×0000 0002:attribute_length=2

0×0010:sourcefile_index = #16(“Demo.java”)

SourceFile屬性結(jié)構(gòu)

SourceFile屬性用來(lái)記錄生成該Class文件的源碼文件名稱。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容