<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          超贊,2W 字的Java class類文件結(jié)構(gòu)詳解!

          共 25445字,需瀏覽 51分鐘

           ·

          2021-11-06 11:48

          原文:cnblogs.com/insaneXs/p/13301094.html

          最近在二刷《深入理解Java虛擬機(jī)》的時(shí)候,發(fā)現(xiàn)看類文件結(jié)構(gòu)這章依舊云里霧里。因?yàn)轭愇募械慕Y(jié)構(gòu)實(shí)在太多了,尤其在涉及表的時(shí)候,經(jīng)常會(huì)出現(xiàn)表中嵌套表的情況。

          有學(xué)習(xí)經(jīng)驗(yàn)的同學(xué)一定知道最快了解一種協(xié)議的方法就是參照規(guī)則自己將協(xié)議解析一次。眾所周知類文件中存的也是字節(jié)(所以class文件也叫字節(jié)碼文件),針對(duì)類文件結(jié)構(gòu)的解析就像是對(duì)某種協(xié)議的解析。因此,可以將《深入理解Java虛擬機(jī)》第6章中的內(nèi)容作為工具書來對(duì)照(因?yàn)闀刑峁┝烁鞣N結(jié)構(gòu)的說明和解析規(guī)則),自己寫一個(gè)類,針對(duì)編譯后產(chǎn)生的class文件解析一遍就能對(duì)類文件結(jié)構(gòu)有個(gè)大致的印象了。

          類文件結(jié)構(gòu):

          在開始解析類文件前,需要先對(duì)class文件的大致結(jié)構(gòu)做一個(gè)初步了解,后續(xù)的解析也將會(huì)根據(jù)這個(gè)結(jié)構(gòu)分成幾部分來解析。

          • 校驗(yàn)信息:
            • 魔數(shù):4字節(jié)(固定值0xcafe babe)
            • 次版本號(hào):2字節(jié)
            • 主版本號(hào):2字節(jié),由十進(jìn)制的45開始。
          • 常量池:
            • 常量池計(jì)數(shù)(constant_pool_count):2字節(jié),表示常量池中常量的個(gè)數(shù),其中下標(biāo)為0的常量并不會(huì)出現(xiàn)在常量池中,也就是說常量池中的索引實(shí)際上是從1開始的,之所以要將下標(biāo)為0的常量預(yù)留是為了滿足常量池中某一常量不引用任何常量的情況(Java語言中是否有這種情況還需要確認(rèn))。
            • 常量池(constant_pool):每個(gè)常量的數(shù)據(jù)結(jié)構(gòu)并不是固定的,由常量的類型決定,但是每個(gè)常量的第一個(gè)字節(jié)決定了常量的類型。
          • 類信息:
            • 訪問標(biāo)志(access_flags):2字節(jié),表示類和接口的訪問控制
            • 類信息(this_class):2字節(jié)
            • 父類信息(super_class):2字節(jié)
            • 接口信息,包括接口個(gè)數(shù)(interfaces_count, 2字節(jié))和接口信息(interfaces?一個(gè)interface是2字節(jié),總共interfaces_counts?* 2個(gè)字節(jié))
          • 字段信息:
            • 字段個(gè)數(shù)(fields_count):2字節(jié),表示之后有幾個(gè)field_info的結(jié)構(gòu)
            • 字段表(field_info):字段表,是一個(gè)表結(jié)構(gòu),長度不固定,結(jié)構(gòu)稍復(fù)雜
          • 方法信息:
            • 方法個(gè)數(shù)(methods_count):2字節(jié),表示之后有幾個(gè)(method_info)方法表結(jié)構(gòu)
            • 方法表(methods_info):方法表,長度不固定,可能是最復(fù)雜的一個(gè)結(jié)構(gòu)了。
          • 屬性表信息:
            • 屬性表個(gè)數(shù)(attributes_count):2字節(jié),表示之后有幾個(gè)屬性表結(jié)構(gòu)
            • 屬性表(attribute_info)屬性表信息,表結(jié)構(gòu),長度不固定。屬性表信息可能會(huì)同樣嵌套在字段表,方法表的表結(jié)構(gòu)中。

          對(duì)Class文件解析前的準(zhǔn)備

          對(duì)Class文件的解析首先需要寫一個(gè)類用作測(cè)試,然后編譯該類生成class文件,在針對(duì)該class文件進(jìn)行解析。
          為了更好的對(duì)比解析過程的正確性,我們可以通過javap -p -verbose命令先對(duì)class文件反編譯,輸出結(jié)果。

          Java代碼:

          package?com.insanexs.mess.javap;

          public?class?JavapTest?{

          ????protected?static?final?String?VAR_CONSTANT?=?"CONSTANT";

          ????private?volatile?int?intField;

          ????private?int[]?intArraysField;

          ????private?String?strField;

          ????public?JavapTest(){

          ????}

          ????public?void?publicMethod(){

          ????}

          ????protected?String?protectedReturnStrMethod(){
          ????????return?strField;
          ????}

          ????private?synchronized?void?privateSynchronizedMethod(int?intArgs){
          ????????intField?=?intArgs;
          ????}
          }

          編譯生成的class文件如下(已經(jīng)按各部分拆開):

          classfile 16進(jìn)制顯示

          開始分析

          校驗(yàn)信息

          魔數(shù)

          魔數(shù)是class文件的前四個(gè)字節(jié),固定為0xcafe babe。用途是判斷文件是否正確。

          主次版本號(hào)

          0x0000 0034 其中前兩個(gè)字節(jié)表示次版本號(hào),后兩個(gè)字節(jié)表示主版本號(hào)。0x34轉(zhuǎn)成十進(jìn)制為52,52-45+1 = 8(第一個(gè)主版本號(hào)從45開始),因此推斷出Java版本為JDK 8。主次版本號(hào)用來校驗(yàn)字節(jié)碼和JVM是否匹配,JVM的版本需要大于等于class文件的版本號(hào)。

          常量池

          常量池中存量了兩種類型的常量:字面量和符號(hào)引用。
          字面量可以理解為常量的值(無法修改的值)。
          符號(hào)引用則包括三種類型:類或接口的全限定名,字段的名稱和描述符,方法的名稱和描述符(后兩種情況其實(shí)是很對(duì)稱的,一個(gè)針對(duì)字段另一個(gè)針對(duì)方法)。

          常量池計(jì)數(shù)constant_pool_count

          校驗(yàn)信息后,接下來的兩個(gè)字節(jié)表示常量池中常量的個(gè)數(shù),這里是0x0024,轉(zhuǎn)成十進(jìn)制為36。說明常量池中共有36個(gè)常量。
          但是由于下標(biāo)為0的常量是由JVM故意空出的,不會(huì)顯示出現(xiàn)在字節(jié)碼中,因此實(shí)際常量池的常量從1開始,直到35。

          常量池

          我們已經(jīng)知道了常量池共有36個(gè)常量(實(shí)際只有35個(gè)),但由于不同類型的常量結(jié)構(gòu)并不是固定的,我們無法通過常量個(gè)數(shù)直接推出之后多少字節(jié)屬于常量池的內(nèi)容。
          因此,我們只能逐個(gè)解析常量池中的常量,直到解析出常量的個(gè)數(shù)達(dá)到constant_pool_count - 1,常量池才算解析完成。
          由于篇幅限制,本文不會(huì)解析全部常量池,只會(huì)解析其中一些常量用作示范,讀者可根據(jù)示范自行完成剩下的常量解析。
          上面已經(jīng)說了不同類型的常量其結(jié)構(gòu)也是不同的,但是所有常量的第一個(gè)字節(jié)都是標(biāo)志位,因此解析常量池中的常量的方式是解析一個(gè)字節(jié),根據(jù)該字節(jié)確定常量的類型,在查找對(duì)應(yīng)的結(jié)構(gòu)完成剩余部分的解析。

          譬如,這里的第一個(gè)常量#1:

          第一個(gè)字節(jié):0x0a,對(duì)比常量池的類型后發(fā)現(xiàn)是一個(gè)CONSTANT_Methodref_info(類中方法的符號(hào)引用)。
          在查找該類型的結(jié)構(gòu),應(yīng)該是:

          CONSTANT_Methodref_info?{
          ?u1?tag;
          ?u2?class_index;
          ?u2?name_and_type_index;
          }

          應(yīng)該是一個(gè)字節(jié)的標(biāo)識(shí)位(tag),2個(gè)字節(jié)的類名稱索引(class_index)和2個(gè)字節(jié)的名稱和類型描述符(name_and_type_index),共5個(gè)字節(jié)。
          因此常量池#1對(duì)應(yīng)的字節(jié)分別是:0a 0005 001e(含我們已經(jīng)分析過的0a)。其中class_index?為0x0005,name_and_type_index為0x001e。這兩個(gè)index的值均是指向常量池的其他常量的,轉(zhuǎn)成十進(jìn)制分別是指向#5和#30。
          這樣就解析完了常量池中的第一個(gè)常量。

          //常量#1
          0a //tag 表示類型為Methodref_info(類中方法) 其結(jié)構(gòu)為(U1 flag;U2 index(指向類描述符); U2 index(指向名稱和類型描述符))
          0005 //指向常量池中0x05的常量 => #5
          001e //指向常量池中0x1e的常量 => #30
          同理我們解析常量池中的第二個(gè)常量#2:

          首先,第一個(gè)字節(jié)是0x09,查表確定類型為CONSTANT_Fieldref_info(字段的符號(hào)引用)。確定其結(jié)構(gòu)和CONSTANT_Methodref_info相同:

          CONSTANT_Fieldref_info?{
          ?u1?tag;
          ?u2?class_index;
          ?u2?name_and_type_index;
          }

          同樣解析得:

          //常量#2
          09?//0x09表示類型為Fieldref_info(類中字段)?其結(jié)構(gòu)為(U1?flag;?U2?index(指向類描述符);?U2?index(指向名稱和類型描述))=>
          0004?//指向常量池中0x04的常量?=>#4
          001f?//指向常量池中0x1f的常量?=>#31

          之后的解析過程不再贅述,直接貼上解析常量池#3-#35的結(jié)果:

          //常量#3
          09?//同樣是Fieldref_info
          0004?//?=>#4
          0020?//?=>#32
          //常量#4
          07?//0x07表示Class_info(類或接口的符號(hào)引用)?其結(jié)構(gòu)為(U1?flag;?U2?index(指向全限定名常量))
          0021?//指向常量池中的0x21?=>?#33
          //常量#5
          07?//同樣是Class_info類型
          0022?//?=>?#34
          //常量#6
          01?//0x01表示Utf8_info?表示一個(gè)UTF8字符串常量(U1?flag;?U2?length(字符串占的字節(jié)數(shù));?U1?數(shù)量為length個(gè),表示byte)說明理論上JVM的字符串常量的字節(jié)上線為65535???
          000c?//length?=?0x0c?表示之后12個(gè)字節(jié)是字符串常量字節(jié)內(nèi)容
          5641?525f?434f?4e53?5441?4e54?//UTF-8字符串的內(nèi)容?用工具翻譯成字符串表示為:VAR_CONSTANT
          //常量#7
          01?//同樣是Utf8_info
          0012?//length?=?18
          4c6a?6176?612f?6c61?6e67?2f53?7472?696e?673b?//翻譯成字符串為:Ljava/lang/String;
          //常量#8
          01?//同樣是Utf8_info
          000d?//length?=?13
          436f?6e73?7461?6e74?5661?6c75?65?//翻譯成字符串為:ConstantValue
          //常量#9
          08?//0x08表示String_info?表示字符串字面常量(U1?flag;?U2?index(指向字符串字面量))
          0023?//指向常量池中的0x23?=>#35
          //常量#10
          01?//Utf8_info
          0008?//length?=?8
          696e?7446?6965?6c64?//翻譯成字符串:intField
          //常量#11
          01?//Utf8_info
          0001?//length?=?1
          49?//翻譯成字符串:I
          //常量#12
          01?//Utf8_info
          000e?//length?=?14
          696e?7441?7272?6179?7346?6965?6c64?//翻譯成字符串為:intArraysField
          //常量#13
          01?//Utf8_info
          0002?//length?=?2
          5b49?//翻譯成字符串為:[I
          //常量#14
          01?//Utf8_info
          0008?//length?=?8
          7374?7246?6965?6c64?//翻譯成字符串為:strField
          //常量#15
          01?//Utf8_info
          0006?//length?=?6
          3c69?6e69?743e?//翻譯成字符串為:
          //常量#16
          01?//Utf8_info
          0003?//length?=?3
          2829?56?//翻譯成字符串為:()V
          //常量#17
          01?//Utf8_info
          0004?//?length?=?4
          436f?6465?//翻譯成字符串為:Code
          //常量#18
          01?//Utf8_info
          000f?//length?=?15
          4c69?6e65?4e75?6d62?6572?5461?626c?65?//翻譯成字符串為:LineNumberTable
          //常量#19
          01?//Utf8_info
          0012?//length?=?18
          4c6f?6361?6c56?6172?6961?626c?6554?6162?6c65?//翻譯成字符串為:LocalVariableTable
          /常量#20
          01?//Utf8_info
          0004?//length?=?4
          7468?6973?//翻譯成字符串為:this
          //常量#21
          01?//Utf8_info
          0023?//length?=?35
          4c?636f?6d2f?696e?7361?6e65?7873?2f6d?6573?732f?6a61?7661?702f?4a61?7661?7054?6573?743b?//翻譯成字符串為:Lcom/insanexs/mess/javap/JavapTest;
          //常量#22
          01?//Utf8_info
          000c?//length?=?12
          70?7562?6c69?634d?6574?686f?64?//翻譯成字符串為:publicMethod
          //常量#23
          01?//Utf8_info
          0018?//length?=?24
          7072?6f74?6563?7465?6452?6574?7572?6e53?7472?4d65?7468?6f64?//翻譯成字符串為:protectedReturnStrMethod
          //常量#24
          01?//Utf8_info
          0014?//length?=?20
          28?294c?6a61?7661?2f6c?616e?672f?5374?7269?6e67?3b?//翻譯成字符串為:()Ljava/lang/String;
          //常量#25
          01?//Utf8_info
          0019?//length?=?25
          7072?6976?6174?6553?796e?6368?726f?6e69?7a65?644d?6574?686f?64?//翻譯成字符串為:privateSynchronizedMethod
          //常量#26
          01?//Utf8_info
          0004?//length?=?4
          2849?2956?//翻譯成字符串為:(I)V
          //常量#27
          01?//Utf8_info
          0007?//length?=?7
          69?6e74?4172?6773?//翻譯成字符串為:intArgs
          //常量#28
          01?//Utf8_info
          000a?//length?=?10
          53?6f75?7263?6546?696c?65?//翻譯成字符串為:SourceFile
          //常量#29
          01?//Utf8_info
          000e?//length?=?14
          4a61?7661?7054?6573?742e?6a61?7661?//翻譯成字符串為:JavapTest.java
          //常量#30
          0c?//0x0c表示NameAndType_info?表示字段或方法的部分符號(hào)引用(U1?flag;?U2?index(指向字段或方法的名稱常量);?U2?index(指向字段或方法的描述符常量))
          000f?//指向常量池中的0x0f?=>?#15
          0010?//指向常量池中的0x10?=>?#16
          //常量#31
          0c?//同樣是NameAndType_info
          000e?//指向常量池中的0x0e?=>?#14
          0007?//指向常量池中的0x07?=>?#7
          //常量#32
          0c?//同樣是NameAndType_info
          000a?//指向常量池中的0x0a?=>?#10
          000b?//指向常量池中的0x0b?=>?#11
          //常量#33
          01?//Utf8_info
          0021?//length?=?33
          636f?6d2f?696e?7361?6e65?7873?2f6d?6573?732f?6a61?7661?702f?4a61?7661?7054?6573?74?//翻譯成字符串為:com/insanexs/mess/javap/JavapTest
          //常量#34
          01?//Utf8_info
          0010?//length?=?16
          6a61?7661?2f6c?616e?672f?4f62?6a65?6374?//翻譯成字符串為:java/lang/Object
          //常量#35
          01?//Utf8_info
          0008?//length?=?8
          43?4f4e?5354?414e?54?//翻譯成字符串為:CONSTANT

          上述常量池得很多常量都直接(或是比較直接的)出現(xiàn)在我們得代碼中,比如字段名稱,類名稱,方法全限定名等。有一部分是根據(jù)代碼能推測(cè),比如方法描述符等,但是還有一部分似乎不明所以,比如上述的CODE,LineNumberTable等。
          不著急,這些常量在之后得解析中會(huì)再次遇到。

          類信息

          在解析完常量池的數(shù)據(jù)后,接下來的一部分?jǐn)?shù)據(jù)表示類得一些信息。從上述得字節(jié)碼得0x0021開始(29行的最后一組)
          類信息這部分主要有類的訪問控制屬性,類索引和父類索引(指向常量池中的常量,通常是類得全限定名),接口個(gè)數(shù)和接口索引(接口索引同樣指向常量池中的常量)。

          訪問控制access_flag

          訪問控制占兩個(gè)字節(jié)(16位,每一個(gè)二進(jìn)制代表一種標(biāo)志,因此理論上最多能有16種標(biāo)志,Java SE 8中定義了8種)。上述文件中對(duì)應(yīng)訪問控制標(biāo)志的字節(jié)位0x0021。0x0021 = (0x0020 | 0x0001),查表得出ACC_SUPER,ACC_PUBLIC這個(gè)類的訪問控制標(biāo)志位?,F(xiàn)代版本編譯的類都會(huì)帶有ACC_SUPER,而ACC_PUBLIC表示這個(gè)類是public的。

          類索引this_class

          類索引占兩個(gè)字節(jié),同樣指向常量池中的常量(類型為類的符號(hào)飲用)。這里對(duì)應(yīng)的數(shù)據(jù)為:

          0004 //this_class U2,指向常量池中的Class_info 這里指向常量池#4

          父類索引super_class

          父類索引和類索引類似,同樣占兩個(gè)字節(jié),指向常量池中的常量,只不過指向的類的符號(hào)飲用代碼的是父類,這里對(duì)應(yīng)的數(shù)據(jù)為:

          0005 //super_class U2 同樣指向常量池中的Class_info 這里指向常量池#5

          接口個(gè)數(shù)interfaces_count和接口索引interface

          之后的兩個(gè)字節(jié)表示類實(shí)現(xiàn)的接口的個(gè)數(shù),然后對(duì)應(yīng)的interfaces_count * 2個(gè)字節(jié)表示接口的索引數(shù)據(jù)。由于我們的測(cè)試類沒有實(shí)現(xiàn)任何接口,因此interfaces_counts為0,之后也沒有表示接口數(shù)據(jù)的字節(jié)。

          0000 //interface_count 表示接口的個(gè)數(shù) 這里為0 表示類沒有實(shí)現(xiàn)接口,之后也沒有字節(jié)表示接口索引

          字段信息

          分析完類信息后,之后的數(shù)據(jù)表示類中字段的信息。這里分為兩部分:字段個(gè)數(shù)field_count和字段表field_info。

          字段個(gè)數(shù)field_count

          字段個(gè)數(shù)表示后面將會(huì)有幾個(gè)字段表結(jié)構(gòu),因?yàn)樽侄伪斫Y(jié)構(gòu)長度也不是固定的,因此也只能解析完所有的字段表后才能繼續(xù)解析下一部分內(nèi)容,無法直接通過字段個(gè)數(shù)推出之后的多少字節(jié)表示字段信息相關(guān)的數(shù)據(jù)。
          字段個(gè)數(shù)占兩個(gè)字節(jié)。

          0004 //field_count 表示字段的個(gè)數(shù) 這里為4 表示接下來的4個(gè)字段表結(jié)構(gòu)

          字段表field_info

          字段表接口較為復(fù)雜,因?yàn)槠涫且粋€(gè)表接口,且可能嵌套其他表。
          先來看一下字段表的接口:

          field_info?{
          ?u2?access_flags;
          ?u2?name_index;
          ?u2?descriptor_index;
          ?u2?attributes_count;
          ?attribute_info?attributes[attributes_count];
          }

          access_flags占兩字節(jié),表示字段的訪問屬性,name_index占兩字節(jié),指向常量池中的常量,表示字段名稱,descriptor_index占兩字節(jié),同樣指向常量池中的常量,表示字段的描述符,attributes_count占兩字節(jié),表示后面有幾個(gè)屬性表,attribute_info即屬性表,用來描述額外屬性,為表結(jié)構(gòu),長度不固定。

          前文已知我們的測(cè)試類會(huì)有4個(gè)字段表,我們這里只針對(duì)第一個(gè)字段表分析,后續(xù)的讀者可以自己按規(guī)則解析。
          首先分析固定的前八個(gè)字節(jié):

          001c //access_flags 字段的訪問屬性 0x1c = (0x10 | 0x08 | 0x04 ) =>ACC_PROTECTED ACC_FINAL ACC_STATIC
          0006 //name_index 指向常量池中#6 即變量名為VAR_CONSTANT
          0007 //descriptor_index 指向常量池#7 即描述符Ljava/lang/String; 說明是String類型的字段
          0001 //attributes_count 表示有1個(gè)attribute_info

          如果某個(gè)字段代表的attributes_count的字節(jié)值為0,那么對(duì)這個(gè)字段的解析就已經(jīng)完成了,但是好巧不巧的,這里分析的attributes_count為1,說明之后還有一些字節(jié)是用來表示屬性表的。
          屬性表用來描述某些特定的額外信息,其整個(gè)結(jié)構(gòu)并非是固定長度的,甚至可能屬性表中嵌套屬性表的情況。
          了解下屬性表的通用結(jié)構(gòu):

          attribute_info?{
          ?u2?attribute_name_index;
          ?u4?attribute_length;
          ?u1?info[attribute_length];
          }

          attribute_name_index:2字節(jié),指向常量池中的常量,表示常量的名稱(Java SE 8規(guī)定能識(shí)別的常量類型有23種)。
          attribute_length:4字節(jié),表示之后還有多少長度的字節(jié)均數(shù)據(jù)該屬性表的內(nèi)容
          info:不固定長字節(jié),解析方式由屬性表的類型決定。
          可以看到前六個(gè)字節(jié)都是相同的,然后由attribute_length表示之后還有多少個(gè)字節(jié),這一點(diǎn)和一些變長協(xié)議類似。
          回到我們需要解析的數(shù)據(jù)中來,屬于字段1中屬性表的字節(jié)應(yīng)該是0008 0000 0002 0009。
          通過解析前兩個(gè)字節(jié)0008,我們得知其指向常量池中的#8,為即ConstantValue。這是當(dāng)字段被final修飾后,出現(xiàn)在字段中的屬性表,表示一個(gè)常量。該屬性表的結(jié)構(gòu)如下:

          ConstantValue_attribute?{
          ?u2?attribute_name_index;
          ?u4?attribute_length;
          ?u2?constantvalue_index;
          }

          可以看到其代表attribute_length的四字節(jié)數(shù)據(jù)為0000 0002(因?yàn)?code style="margin-right: 2px;margin-left: 2px;padding-right: 2px;padding-left: 2px;outline: 0px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);border-radius: 2px;height: 21px;line-height: 22px;">constantvalue_index的長度固定為2),對(duì)比我們的字節(jié)數(shù)據(jù)也確實(shí)如此。然后我們解析代表constantvalue_index的字節(jié)0009,表示指向常量池中#9,為CONSTANT。正好是常量的值。

          之后還剩三個(gè)字段,有興趣的讀者可以自己分析,這里直接貼上結(jié)果:

          //field1
          001c?//access_flags?字段的訪問屬性?0x1c?=?(0x10?|?0x08?|?0x04?)?=>ACC_PROTECTED?ACC_FINAL?ACC_STATIC
          0006?//name_index?指向常量池中0x06?=>?#6?即VAR_CONSTANT
          0007?//descriptor_index?指向常量池中0x07?=>?#7?即Ljava/lang/String;?說明是String類型的字段
          0001?//attributes_count?表示有1個(gè)attribute_info?屬性表?attribute_info是一個(gè)比較復(fù)雜的結(jié)構(gòu),虛擬機(jī)規(guī)范中定義了虛擬機(jī)應(yīng)當(dāng)識(shí)別的二十多種屬性(Java?SE?8?23種)所有屬性的開始的6字節(jié)都是相同的(U2?attribute_name_index?+?U4?attribute_length),之后的結(jié)構(gòu)由屬性自己定義,屬性表可以出現(xiàn)在類,字段及方法上
          0008?//attribute_name_index?指向常量池中的Utf8_info常量?0x08?=>?#8?即ConstantValue?ConstantValue是屬性表的一種,出現(xiàn)在字段中,表示final定義的常量值
          0000?0002?//length?=?2?表示后面2個(gè)字節(jié)長度的數(shù)據(jù)為該屬性表的數(shù)據(jù)
          0009?//對(duì)于ConstantValue而言?這部分?jǐn)?shù)據(jù)表示constantvalue_index?指向常量池中的常量?0x09即?=>#9?即String_info?具體值為#35?為字符串?CONSTANT

          //filed2
          0042?//access_flags?=>(0x40?|?0x02)?=>?ACC_PRIVATE?ACC_VOLATILE
          000a?//name_index?指向常量池中的0x0a?#10?即intField
          000b?//descriptor_index?指向常量池中的0x0b?#11?即I?表示int類型的field
          0000?//attribute_count?=?0?說明無attribute_info

          //filed3
          0002?//access_flags?=>?0x02?=>ACC_PRIVATE
          000c?//name_index?指向常量池中的0x0c?#12?即intArraysField
          000d?//descriptor_index?指向常量池中的0x0d?#13?即[I?表示int數(shù)組
          0000?//attribute_count?=?0?說明無attribute_info

          //field4
          0002?//access_flags?=>?0x02?=>ACC_PRIVATE
          000e?//name_index?指向常量池中的0x0e?#14?即strField
          0007?//descriptor_index?指向常量池中的0x07?#7?即?Ljava/lang/String;?說明是String類型的字段
          0000?//attribute_count?=?0?說明無attribute_info

          方法信息

          解析完字段信息,之后的字節(jié)是從0x0004開始(32行最后一個(gè)字節(jié)和33行第一個(gè)字節(jié)),方法信息和字段信息的解析其實(shí)很對(duì)成,同樣顯示通過methods_count表示之后有幾個(gè)方法表,再逐個(gè)解析方法表method_info,直到達(dá)到方法個(gè)數(shù)。方法表的整體結(jié)構(gòu)和字段表的整體結(jié)構(gòu)也是類似的,只是方法表上的屬性表會(huì)更多,因此,解析起來要比字段復(fù)雜。

          方法個(gè)數(shù)methods_count

          methods_count占兩個(gè)字節(jié),表示之后共有多少個(gè)方法表。0004表示之后有四個(gè)方法表。

          方法表method_info

          方法表和字段表結(jié)構(gòu)是對(duì)稱的,通用結(jié)構(gòu)如下:

          method_info?{
          ?u2?access_flags;
          ?u2?name_index;
          ?u2?descriptor_index;
          ?u2?attributes_count;
          ?attribute_info?attributes[attributes_count];
          }

          可以看到其結(jié)構(gòu)和field_info是相同的,只不過name_index指向常量池中表示的方法名稱的常量,descriptor_index則指向常量池中表示方法描述符的常量。

          我們以第一個(gè)方法表為例,先解析前八個(gè)固定的字節(jié)。
          access_flag0001,對(duì)應(yīng)標(biāo)識(shí)位為ACC_PUBLIC,表示為共有方法。
          name_index對(duì)應(yīng)的字節(jié)為000f,指向常量池#15,即方法名為:,
          descriptor_index0010,指向常量池的#16,即方法描述符為:()V 無參無返回。

          attributes_count0001,表示之后有一個(gè)attribute_info屬性表。
          屬性表的解析方式在解析字段過程時(shí),已經(jīng)介紹過了。
          首先是根據(jù)前兩字節(jié)確認(rèn)屬性表的類型:0011,指向常量池中#17,發(fā)現(xiàn)是我們之前不明所以的CODE常量,原來是一種屬性表的類型。

          ?

          CODE屬性表是很重要的一部分信息,因?yàn)樗蟹椒ǖ膱?zhí)行邏輯(代碼塊)。

          ?

          查看CODE屬性表的結(jié)構(gòu):

          Code_attribute?{
          ?u2?attribute_name_index;
          ?u4?attribute_length;
          ?u2?max_stack;
          ?u2?max_locals;
          ?u4?code_length;
          ?u1?code[code_length];
          ?u2?exception_table_length;
          ?{?u2?start_pc;
          ?u2?end_pc;
          ?u2?handler_pc;
          ?u2?catch_type;
          ?}?exception_table[exception_table_length];
          ?u2?attributes_count;
          ?attribute_info?attributes[attributes_count];
          }

          可以看到CODE屬性表是一個(gè)非固定長度的結(jié)構(gòu)。

          先看相對(duì)簡單的部分:

          attribute_name_index:屬性表名稱索引,2字節(jié),表示屬性表的類型。
          attribute_length:屬性長度,4字節(jié),表示之后多少個(gè)字節(jié)的內(nèi)容均屬于該屬性表。對(duì)于CODE這種非固定長度的屬性表結(jié)構(gòu)而言,長度顯得格外重要。
          max_stack:2字節(jié),表示操作數(shù)棧的最大深度(注意和方法的調(diào)用棧深度是不同的概念)。
          max_locals:2字節(jié),局部變量表的大小,單位是slot,一個(gè)slot可以存放32位長度的數(shù)據(jù),但是像long和double類型的變量,需要使用兩個(gè)slot。
          code_length:字節(jié)碼長度,表示之后n個(gè)字節(jié)均和JVM字節(jié)碼指令相關(guān)。
          code:字節(jié)碼,JVM字節(jié)碼指令占一個(gè)字節(jié)。但是部分字節(jié)碼指令需要帶上參數(shù)(因此消耗了部分字節(jié)數(shù)據(jù))。

          參考例子,對(duì)這部分結(jié)果的解析如下:

          0011?//attribute_name_index?指向常量池中的0x11?#17?即Code?表示CODE屬性表?CODE屬性表結(jié)構(gòu)為(U2?attribute_name_index?+?U4?attribute_length?+?U2?max_stack?+?U2?max_locals?+?U4?code_length?+?code_length?*?U1?code?+??U2?exception_table_length?+?exception_table_length?*?exception_info?+?U2?attribute_count?+?attribute_count?*?attribute_info)
          0000?0033?//length?=?0x33?表示之后51個(gè)字節(jié)為CODE屬性表中的信息
          0001?//max_stack?1
          0001?//max_locals?1
          0000?0005?//code_length?5?后面接CODE?每個(gè)指令占一個(gè)字節(jié)?部分指令后的字節(jié)表示指令的參數(shù)
          2a?b7?00?01?b1?//aload_0?invokespecial?(0001?=>?常量池#1)?return

          需要注意的是指令部分b7后面兩個(gè)字節(jié)0001是b7(invokespecial)需要的參數(shù),指向常量池#1。因此并非CODE中的每一個(gè)字節(jié)都是指令集中的指令。

          對(duì)異常表的解析

          我們這里的例子并沒有涉及異常處理的代碼,因此exception_table_length對(duì)應(yīng)的字節(jié)是0000,長度為0,說明之后沒有異常表。
          讀者如果感興趣可以自己寫代碼測(cè)試,分析方法類似。

          CODE中嵌套屬性表的解析

          方法表結(jié)構(gòu)之所以復(fù)雜,正是因?yàn)榻?jīng)常出現(xiàn)表嵌套表的情況。CODE屬性表就可能含有其他屬性表。
          同理,我們解析之后表示attributes_count的兩個(gè)字節(jié)——0002。一看還不少,我激動(dòng)的咽了咽口水,解析不怕多,就是干!

          第一個(gè)屬性表的類型索引0012,表示指向常量池#18,為LineNumberTable(又出現(xiàn)了一個(gè)我們之前不明所以的常量,看來這些常量多半是屬性表的類型),表示指令和行號(hào)的對(duì)應(yīng)關(guān)系。查找其結(jié)構(gòu)如下:

          LineNumberTable_attribute?{
          ?u2?attribute_name_index;
          ?u4?attribute_length;
          ?u2?line_number_table_length;
          ?{?u2?start_pc;
          ?u2?line_number;
          ?}?line_number_table[line_number_table_length];
          }

          得,結(jié)構(gòu)中前面幾個(gè)還好,后面又嵌套了一個(gè)比較復(fù)雜的屬性。但是沒在怕。
          attribute_name_index:這個(gè)現(xiàn)在已經(jīng)很面熟了,表示屬性表的名稱索引。
          attribute_length:這個(gè)也是老伙計(jì),后面多少字節(jié)依舊是該屬性表得內(nèi)容。
          line_number_table_length:4字節(jié),表示代碼行號(hào)表的個(gè)數(shù),之后有幾個(gè)line_number_table。
          line_number_table:代碼行號(hào)表,一個(gè)復(fù)雜結(jié)構(gòu),但是好在是固定的,占4字節(jié),前兩字節(jié)start_pc表示一個(gè)偏移量,應(yīng)該是對(duì)著之前分析的CODE屬性表中的code部分順序(要注意區(qū)分這里的兩個(gè)CODE,大寫的CODE表示屬性表的一種類型,小寫的code表示CODE屬性表中表示字節(jié)碼指令的部分)。后兩個(gè)字節(jié)line_number表示指令對(duì)應(yīng)出現(xiàn)在代碼中的行號(hào)。

          0012?//attribute_name_index?指向常量池中的0x12?即#18?LineNumberTable?表示行號(hào)和字節(jié)碼指令的對(duì)應(yīng)關(guān)系
          0000?000a?//attribute_length?表示后面10個(gè)字節(jié)均為該屬性表的信息
          0002?//line_number_table_length?表示后面有2個(gè)line_number_table?一個(gè)line_number_table結(jié)構(gòu)為(U2?start_pc?+?U2?line_number)
          0000?//start_pc?start_pc表示上述指令集中的索引?0對(duì)應(yīng)上述指令集既為2a?為aload_0指令
          000d?//line_number?等于行號(hào)line:13?表示aload_0?對(duì)應(yīng)代碼13行
          0004?//start_pc?同理對(duì)應(yīng)索引為4的指令?return
          000f?//line_number?等于行號(hào)line:15?表示return?對(duì)應(yīng)代碼15行

          在解析第二個(gè)屬性表:attribute_name_index代表的字節(jié)是0013,對(duì)應(yīng)常量池#19,為LocalVariableTable 表示方法局部變量的描述。
          其結(jié)構(gòu)如下:

          LocalVariableTable_attribute?{
          ?u2?attribute_name_index;
          ?u4?attribute_length;
          ?u2?local_variable_table_length;
          ?{?u2?start_pc;
          ?u2?length;
          ?u2?name_index;
          ?u2?descriptor_index;
          ?u2?index;
          ?}?local_variable_table[local_variable_table_length];
          }

          前兩個(gè)部分已經(jīng)很熟悉了不再贅述。
          local_variable_table_length2字節(jié),表示局部變量表的個(gè)數(shù)。
          local_variable_table是一個(gè)復(fù)雜結(jié)構(gòu),但也是固定長度的,共10字節(jié)。
          start_pc占2字節(jié),表示偏移量,length占2字節(jié),表示長度,這兩部分信息結(jié)合起來可以確認(rèn)變量的作用域是從start_pcstart_pc+length
          name_index表示2字節(jié),變量的名稱索引,指向常量池中的常量。descriptor_index表示2字節(jié),變量的描述符索引,指向常量池中的常量。這兩部分信息結(jié)合起來可以確定變量的名稱和類型。
          index表示變量在局部變量表中的索引,前文已經(jīng)介紹過了變量存儲(chǔ)在局部變量中是以slot為單位,這里的index就表示該變量存放在第幾個(gè)slot。

          理論分析了,結(jié)合情況實(shí)踐一下:

          0013?//attribute_name_index?指向常量池中的0x13?即#19??LocalVariableTable?表示方法局部變量的描述
          0000?000c?//attribute_length?表示之后12個(gè)字節(jié)均為?LocalVariableTable?屬性表中的內(nèi)容
          0001?//local_variable_table_length?表示有一個(gè)局部變量表?local_variable_table的結(jié)構(gòu)為(U2?start_pc?+?U2?length?+?U2?name_index?+?U2?descriptor_index?+?U2?index)
          0000?//start_pc?0
          0005?//length?5?說明該局部變量從偏移量0開始到0+5?一直被使用
          0014?//name_index?指向常量池中的常量?0x14?=>?#20?即this
          0015?//descriptor_index?指向常量池中的常量?0x15?=>?#21?即Lcom/insanexs/mess/javap/JavapTest;
          0000?//index?0
          ?

          補(bǔ)充說明一點(diǎn):類中的非靜態(tài)方法 虛擬機(jī)會(huì)默認(rèn)將this指針作為方法的第一個(gè)變量。

          ?

          這樣我們第一個(gè)方法就解析完成了,讀者感興趣的可以針對(duì)剩下的三個(gè)方法實(shí)操一下,這里就直接貼上解析結(jié)果:

          //method_1
          0001?//access_flag?=>?0x01?=>?ACC_PUBLIC
          000f?//name_index 指向常量池中的0x0f #15 即方法名為:
          0010?//descriptor_index 指向常量池中的0x10?#16 即方法描述符為:()V 無參無返回
          0001?//attributes_count?表示之后有一個(gè)attribute_info
          0011?//attribute_name_index?指向常量池中的0x11?#17?即Code?表示CODE屬性表?CODE屬性表結(jié)構(gòu)為(U2?attribute_name_index?+?U4?attribute_length?+?U2?max_stack?+?U2?max_locals?+?U4?code_length?+?code_length?*?U1?code?+??U2?exception_table_length?+?exception_table_length?*?exception_info?+?U2?attribute_count?+?attribute_count?*?attribute_info)
          0000?0033?//length?=?0x33?表示之后51個(gè)字節(jié)為CODE屬性表中的信息
          0001?//max_stack?1
          0001?//max_locals?1
          0000?0005?//code_length?5?后面接CODE?每個(gè)指令占一個(gè)字節(jié)?部分指令后的字節(jié)表示指令的參數(shù)
          2a?b7?00?01?b1?//aload_0?invokespecial?(0001?=>?常量池#1)?return
          0000?//exception_table_length?=?0?說明沒有異常表的數(shù)據(jù)?如果exception_table_length為n?后面的n個(gè)字節(jié)為異常表相關(guān)的信息
          0002?//attributes_count?=?2
          0012?//attribute_name_index?指向常量池中的0x12?即#18?LineNumberTable?表示行號(hào)和字節(jié)碼指令的對(duì)應(yīng)關(guān)系
          0000?000a?//attribute_length?表示后面10個(gè)字節(jié)均為該屬性表的信息
          0002?//line_number_table_length?表示后面有2個(gè)line_number_table?一個(gè)line_number_table結(jié)構(gòu)為(U2?start_pc?+?U2?line_number)
          0000?//start_pc?start_pc表示上述指令集中的索引?0對(duì)應(yīng)上述指令集既為2a?為aload_0指令
          000d?//line_number?等于行號(hào)line:13?表示aload_0?對(duì)應(yīng)代碼13行
          0004?//start_pc?同理對(duì)應(yīng)索引為4的指令?return
          000f?//line_number?等于行號(hào)line:15?表示return?對(duì)應(yīng)代碼15行

          0013?//attribute_name_index?指向常量池中的0x13?即#19??LocalVariableTable?表示方法局部變量的描述
          0000?000c?//attribute_length?表示之后12個(gè)字節(jié)均為?LocalVariableTable?屬性表中的內(nèi)容
          0001?//local_variable_table_length?表示有一個(gè)局部變量表?local_variable_table的結(jié)構(gòu)為(U2?start_pc?+?U2?length?+?U2?name_index?+?U2?descriptor_index?+?U2?index)
          0000?//start_pc?0
          0005?//length?5?說明該局部變量從偏移量0開始到0+5?一直被使用
          0014?//name_index?指向常量池中的常量?0x14?=>?#20?即this
          0015?//descriptor_index?指向常量池中的常量?0x15?=>?#21?即Lcom/insanexs/mess/javap/JavapTest;
          0000?//index?0

          //method_2
          0001?//access_flag?=>0x01?=>ACC_PUBLIC
          0016?//name_index?指向常量池中的0x16?=>#22?即??publicMethod
          0010?//descriptor_index?指向常量池中的0x10?=>#16?即?()V?表示無參且無返回值
          0001?//attribute_count?表示之后有1個(gè)attributes_info
          0011?//attribute_name_index?同樣指向常量中的0x11?#17即CODE屬性表
          0000?002b?//length?=?43?表示之后43個(gè)字節(jié)為CODE屬性表的內(nèi)容
          0000?//max_stack?=?0
          0001?//max_locals?=?1
          0000?0001?//code_length?=?1
          b1?//指令?表示return
          0000?//exception_table_length?=?0?無異常表
          0002?//attributes_count?=?2
          0012?//attribute_name_index?指向常量池中的#18?LineNumberTable
          0000?0006?//attribute_length?表示后6個(gè)字節(jié)為LineNumberTable的信息
          0001?//表示只有一個(gè)line_number_table
          0000?//start_pc?0?對(duì)應(yīng)指令return
          0013?//line_number?19??表示return對(duì)應(yīng)的行號(hào)是19
          0013?//attribute_name_index?指向常量池中的#19?LocalVariableTable
          0000?000c?//attribute_length?表示之后12個(gè)字節(jié)均為?LocalVariableTable?屬性表中的內(nèi)容
          0001?//local_variable_table_length?表示有1個(gè)局部變量表
          0000?//start_pc?0
          0001?//length?1
          0014?//name_index?指向常量池中的常量?0x14?=>?#20?即this
          0015?//descriptor_index?指向常量池中的常量?0x15?=>?#21?即Lcom/insanexs/mess/javap/JavapTest;
          0000?//index?0

          //method?3
          0004?//access_flag?=>0x04?=>ACC_PROTECTED
          0017?//name_index?常量池中#23?即?protectedReturnStrMethod
          0018?//descriptor_index?常量池中#24?()Ljava/lang/String;?表示無參,單接返回值類型為String
          0001?//attribute_count?表示有一個(gè)attribute_info
          0011?//attribute_name_index?同樣指向常量中的0x11?#17即CODE屬性表
          0000?002f?//length?=?47?之后47個(gè)字節(jié)均為CODE屬性表的內(nèi)容
          0001?//max_stack?=?1
          0001?//max_locals?=?1
          0000?0005?//code_length?=?5?表示方法含有五個(gè)指令
          2a?b4?00?02?b0?//字節(jié)碼指令?分別表示aload_0?getfield?(0002?=>常量池#2)?areturn
          0000?//exception_table_length?=?0?表示無異常表
          0002?//attributes_count表示有兩個(gè)屬性表
          0012?//attribute_name_index?常量池#18?LineNumberTable
          0000?0006?//attribute_length?表示后6個(gè)字節(jié)為LineNumberTable的信息
          0001?//表示只有一個(gè)line_number_table
          0000?//start_pc?0?對(duì)應(yīng)的指令aload_0
          0016?//line_number?對(duì)應(yīng)line:22
          0013?//attribute_name_index?常量池#19?LocalVariableTable
          0000?000c?//attribute_length?表示之后12個(gè)字節(jié)均為?LocalVariableTable?屬性表中的內(nèi)容
          0001?//local_variable_table_length?表示有1個(gè)局部變量表
          0000?//start_pc?0
          0005?//length?5
          0014?//name_index?指向常量池#20?即this
          0015?//descriptor_index?指向常量池#21?即Lcom/insanexs/mess/javap/JavapTest;
          0000?//index?0

          //method?4
          0022?//access_flag?=>?(0x20?|?0x02)?=>?ACC_SYNCHRONIZED?ACC_PRIVATE
          0019?//name_index?常量池中#25?即?privateSynchronizedMethod
          001a?//?descriptor_index?常量池中#26?(I)V?接受一個(gè)int參數(shù)?但無返回值
          0001?//attribute_count?表示有一個(gè)attribute_info
          0011?//attribute_name_index?同樣指向常量中的0x11?#17即CODE屬性表
          0000?003e?//length?=?62?之后的62個(gè)字節(jié)均為CODE屬性
          0002?//max_stack?=?2
          0002?//max_locals?=?2
          0000?0006?//code_length?=?6?表示之后6個(gè)字節(jié)均為字節(jié)碼指令
          2a?1b?b5?00?03?b1?//分別為aload_0?iload_1?putfield?(0003?=>常量池#3)?return
          0000?//exception_table_length?=?0?表示無異常表
          0002?//attributes_count表示有兩個(gè)屬性表
          0012?//attribute_name_index?常量池#18?LineNumberTable
          0000?000a?//attribute_length?表示后10個(gè)字節(jié)為LineNumberTable的信息
          0002?//表示有2個(gè)line_number_table
          0000?//start_pc?0?對(duì)應(yīng)的指令aload_0
          001a?//line_number?對(duì)應(yīng)line:26
          0005?//start_pc?5?對(duì)應(yīng)的指令return
          001b?//line_number?對(duì)應(yīng)line:27
          0013?//attribute_name_index?常量池#19?LocalVariableTable
          0000?0016?//attribute_length?=?22?之后22個(gè)字節(jié)均為局部變量表的內(nèi)容
          0002?//local_variable_table_length?=?2?表示存在兩個(gè)局部變量表
          0000?//start_pc?0
          0006?//length?6
          0014?//name_index?常量池#20?即this
          0015?//descriptor_index?指向常量池#21?即Lcom/insanexs/mess/javap/JavapTest;
          0000?//index?0
          0000?//start_pc?0
          0006?//length?6
          001b?//name_index?指向常量池#27?即intArgs
          000b?//descriptor_index?指向常量池#11?即I?表示int類型
          0001?//index?1

          屬性表信息

          在解析完方法后,剩下還有一小部分內(nèi)容是屬性表信息?,F(xiàn)在我們對(duì)屬性表的解析過程可以說是輕車熟路了。
          未解析的字節(jié)并不多了:

          0001?001c?0000?0002?001d

          首先attributes_count0001,說明只有一個(gè)屬性表,so easy!
          attributes_name_index001c,指向常量池#28,即SourceFile 用于記錄源文件名稱。
          SourceFile的結(jié)構(gòu)如下:

          SourceFile_attribute?{
          ?u2?attribute_name_index;
          ?u4?attribute_length;
          ?u2?sourcefile_index;
          }

          定長的結(jié)構(gòu),最后2個(gè)字節(jié)表示源文件名稱索引,指向常量池。這里為001d,指向常量池中#29,為JavapTest.java。

          這樣,我們就完成了所有的字節(jié)碼文件解析。完成的解析結(jié)果和源碼可以私我微信:codeq,免費(fèi)獲??!。

          其他問題的小測(cè)試

          這里針對(duì)類文件結(jié)構(gòu)學(xué)習(xí)過程中,幾個(gè)疑問做了下測(cè)試。

          同步方法和同步塊在類文件結(jié)構(gòu)中的表示有什么不同?

          上文我們已經(jīng)測(cè)了同步方法,其是通過方法的access_flag的標(biāo)志位(ACC_SYNCHRONIZED)表示的。但是這種方式對(duì)于同步塊而言已經(jīng)是不行的,那么同步塊是如何實(shí)現(xiàn)同步控制的呢?
          測(cè)試代碼類如下:

          public?class?SynchronizedTest?{

          ????public?synchronized?void?synchronizedMethod(){
          ????????return;
          ????}

          ????public?static?synchronized?void?staticSynchronizedMethod(){
          ????????return;
          ????}

          ????public?void?synchronizedCode(){
          ????????synchronized?(this){
          ????????????return;
          ????????}
          ????}

          ????public?void?staticSynchronizedCode(){
          ????????synchronized?(SynchronizedTest.class){
          ????????????return;
          ????????}
          ????}
          }

          使用javap -verbose -c 反編譯class文件。
          截取相關(guān)四個(gè)方法,如下:

          ??public?synchronized?void?synchronizedMethod();
          ????descriptor:?()V
          ????flags:?ACC_PUBLIC,?ACC_SYNCHRONIZED
          ????Code:
          ??????stack=0,?locals=1,?args_size=1
          ?????????0:?return
          ??????LineNumberTable:
          ????????line?11:?0
          ??????LocalVariableTable:
          ????????Start??Length??Slot??Name???Signature
          ????????????0???????1?????0??this???Lcom/insanexs/mess/javap/SynchronizedTest;

          ??public?static?synchronized?void?staticSynchronizedMethod();
          ????descriptor:?()V
          ????flags:?ACC_PUBLIC,?ACC_STATIC,?ACC_SYNCHRONIZED
          ????Code:
          ??????stack=0,?locals=0,?args_size=0
          ?????????0:?return
          ??????LineNumberTable:
          ????????line?15:?0

          ??public?void?synchronizedCode();
          ????descriptor:?()V
          ????flags:?ACC_PUBLIC
          ????Code:
          ??????stack=2,?locals=3,?args_size=1
          ?????????0:?aload_0
          ?????????1:?dup
          ?????????2:?astore_1
          ?????????3:?monitorenter
          ?????????4:?aload_1
          ?????????5:?monitorexit
          ?????????6:?return
          ?????????7:?astore_2
          ?????????8:?aload_1
          ?????????9:?monitorexit
          ????????10:?aload_2
          ????????11:?athrow
          ??????Exception?table:
          ?????????from????to??target?type
          ?????????????4?????6?????7???any
          ?????????????7????10?????7???any
          ??????LineNumberTable:
          ????????line?19:?0
          ????????line?20:?4
          ????????line?21:?7
          ??????LocalVariableTable:
          ????????Start??Length??Slot??Name???Signature
          ????????????0??????12?????0??this???Lcom/insanexs/mess/javap/SynchronizedTest;
          ??????StackMapTable:?number_of_entries?=?1
          ????????frame_type?=?255?/*?full_frame?*/
          ??????????offset_delta?=?7
          ??????????locals?=?[?class?com/insanexs/mess/javap/SynchronizedTest,?class?java/lang/Object?]
          ??????????stack?
          =?[?class?java/lang/Throwable?]

          ??public?void?staticSynchronizedCode()
          ;
          ????descriptor:?()V
          ????flags:?ACC_PUBLIC
          ????Code:
          ??????stack=2,?locals=3,?args_size=1
          ?????????0:?ldc???????????#2??????????????????//?class?com/insanexs/mess/javap/SynchronizedTest
          ?????????2:?dup
          ?????????3:?astore_1
          ?????????4:?monitorenter
          ?????????5:?aload_1
          ?????????6:?monitorexit
          ?????????7:?return
          ?????????8:?astore_2
          ?????????9:?aload_1
          ????????10:?monitorexit
          ????????11:?aload_2
          ????????12:?athrow
          ??????Exception?table:
          ?????????from????to??target?type
          ?????????????5?????7?????8???any
          ?????????????8????11?????8???any
          ??????LineNumberTable:
          ????????line?25:?0
          ????????line?26:?5
          ????????line?27:?8
          ??????LocalVariableTable:
          ????????Start??Length??Slot??Name???Signature
          ????????????0??????13?????0??this???Lcom/insanexs/mess/javap/SynchronizedTest;
          ??????StackMapTable:?number_of_entries?=?1
          ????????frame_type?=?255?/*?full_frame?*/
          ??????????offset_delta?=?8
          ??????????locals?=?[?class?com/insanexs/mess/javap/SynchronizedTest,?class?java/lang/Object?]
          ??????????stack?
          =?[?class?java/lang/Throwable?]

          從上面可以分析出:無論是類還是實(shí)例的同步方法,都是通過在ACCESS_FLAG中將ACC_SYNCHRONIZED位標(biāo)識(shí)為真實(shí)現(xiàn)的。
          而對(duì)于同步塊,則是通過字節(jié)碼指令中添加monitorenter和monitorexit實(shí)現(xiàn)的。而鎖的對(duì)象則是根據(jù)操作數(shù)棧當(dāng)前的對(duì)象所決定。

          測(cè)試max_stack

          這個(gè)測(cè)試主要是加深對(duì)操作數(shù)棧的理解,設(shè)計(jì)一個(gè)代碼,讓其在類文件結(jié)構(gòu)中的max_stack為2。
          滿足上面要求的測(cè)試代碼如下:

          public?class?MaxStackTest?{

          ????public?int?maxStack2Method(){
          ????????int?var1?=?1;
          ????????int?var2?=?2;
          ????????return?var1?+?var2;
          ????}
          }

          為什么說此時(shí)操作數(shù)棧最大深度為2,因?yàn)槭紫葀ar1從局部變量表中加載到操作數(shù)棧,此時(shí)操作數(shù)棧的深度為1,接著繼續(xù)從局部變量表中將var2加載到操作數(shù)棧,此時(shí)棧深度為2。而后為了計(jì)算和,操作數(shù)棧彈出var2和var1,深度重回0。所以最大深度為2。
          同樣可以拿javap命令反編譯驗(yàn)證:

          ?public?int?maxStack2Method();
          ????descriptor:?()I
          ????flags:?ACC_PUBLIC
          ????Code:
          ??????stack=2,?locals=3,?args_size=1
          ?????????0:?iconst_1
          ?????????1:?istore_1
          ?????????2:?iconst_2
          ?????????3:?istore_2
          ?????????4:?iload_1
          ?????????5:?iload_2
          ?????????6:?iadd
          ?????????7:?ireturn
          ??????LineNumberTable:
          ????????line?11:?0
          ????????line?12:?2
          ????????line?13:?4
          ??????LocalVariableTable:
          ????????Start??Length??Slot??Name???Signature
          ????????????0???????8?????0??this???Lcom/insanexs/mess/javap/MaxStackTest;
          ????????????2???????6?????1??var1???I
          ????????????4???????4?????2??var2???I
          }

          測(cè)試64位虛擬機(jī)下,int,long,reference分別占幾個(gè)slot

          因?yàn)樵?2位虛擬機(jī)下int和reference只占一個(gè)slot,而long和double占兩個(gè)slot,那么這種情況在64位的虛擬機(jī)下是否依舊如此?
          測(cè)試代碼如下:

          public?class?SlotTest?{

          ????private?Object?reference;

          ????public?void?testSlot(){
          ????????int?i?=0;
          ????????long?l?=?1L;
          ????????Object?reference?=?new?Object();

          ????????int?j?=?1;
          ????????System.out.println(i?+?","+?l?+?","?+?reference?+?","?+?j);
          ????}
          }

          通過javap查看局部變量表,發(fā)現(xiàn)除了long是占2個(gè)slot的,其余的像int和reference都只占1個(gè)slot。說明和32位的情況一致。

          仔細(xì)想想,確實(shí)如此。因此在class文件的開頭校驗(yàn)部分只針對(duì)版本號(hào)進(jìn)行了校驗(yàn),并不區(qū)分是32位還是64位,說明二者的編譯規(guī)則應(yīng)該是一致的。


          瀏覽 117
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  色护士影院 | 久久精品色 | 美女被暴草视频在线看 | 无码一级毛片免费视频播放 | 日韩有码第1页 |