国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

完全圖解JVM Class文件結構

ygyooo / 1287人閱讀

摘要:對一個文件的字節碼進行逐行的分析是理解文件結構的最佳方式。本文的目的在于盡可能完整地拆解的字節碼并將其分塊分析,最終得到的圖解結構希望可以幫助到你。字節碼指令的具體含義鑒于與結構是相對獨立的主題不再詳述,后續會再多帶帶深入介紹。

對一個class文件的字節碼進行逐行的分析是理解class文件結構的最佳方式。但是往往復雜的二進制字節碼會讓人望而卻步,或者只有仔細一點點盯著才能保證不花眼。本文的目的在于盡可能完整地拆解JVM的Class字節碼并將其分塊分析,最終得到的圖解結構希望可以幫助到你。

本文參考自來自周志明《深入理解Java虛擬機(第2版)》,拓展內容建議讀者可以閱讀下這本書。
根據這個簡單的例子來說明

以下的例子作為最簡單的一個java程序,通過javac執行編譯,javap來查看它的反編譯結果,當然我們還會更刨根問底地直接使用二進制編輯器查看class文件的二進制字節排布。

> javap -v Test
Classfile /Users/jinhaoplus/Desktop/Test.class
  Last modified 2018-8-12; size 285 bytes
  MD5 checksum eac8f02f8ad176b09bfd89cf15e2ed3d
  Compiled from "Test.java"
public class top.jinhaoplus.demo.Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#15         // java/lang/Object."":()V
   #2 = Fieldref           #3.#16         // top/jinhaoplus/demo/Test.m:I
   #3 = Class              #17            // top/jinhaoplus/demo/Test
   #4 = Class              #18            // java/lang/Object
   #5 = Utf8               m
   #6 = Utf8               I
   #7 = Utf8               
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               inc
  #12 = Utf8               ()I
  #13 = Utf8               SourceFile
  #14 = Utf8               Test.java
  #15 = NameAndType        #7:#8          // "":()V
  #16 = NameAndType        #5:#6          // m:I
  #17 = Utf8               top/jinhaoplus/demo/Test
  #18 = Utf8               java/lang/Object
{
  public int m;
    descriptor: I
    flags: ACC_PUBLIC

  public top.jinhaoplus.demo.Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 3: 0

  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 6: 0
}
SourceFile: "Test.java"

圖解概況

如上的字節碼閱讀起來有諸多障礙,因此我們把上面的字節碼按照字節碼規范定義的class結構分區為不同的顏色塊,不同的分區顏色說明這個區域對應著class結構中的不同區域定義,表示一個整體概念的字節碼在圖中顯示為同一行上:

下面的圖是class文件結構的思維導圖說明,可以跟上述的實際的一個class的分區作簡單的對照:

詳細解釋一下class文件的每個分區

下面詳細解釋一下class文件的每個分區,括號內的數字表示當前區的占位情況,u是字節的意思,如u4表示占4個字節的空間,對應到圖中就是4個方格。

1. magic

magic(u4):魔數,class文件的標識開頭。

CAFEBABE是固定的JVM Class的魔數,也可以認為是眾所周知的Java咖啡Logo的由來。

2. version

version:class版本,主次版本合起來即可確定版本號。

2.1 minor_version(u2):次版本

2.2 major_version(u2):主版本

Class文件的版本為次版本0X0000、主版本0X0034,對應的是10進制的52.0。說明此Class是在JDK_VERSION=52.0(JDK1.8)的編譯器中生成的,同時又可以被版本在JDK_VERSION=52.0及以上的虛擬機上執行(JVM保持了向下兼容性,但是拒絕執行超過它的版本號的Class字節碼)。

3. 常量池:注意是本處的常量池指class字節碼中的常量池而非JVM中的常量池(但后者中的數據其實是加載于前者)。 3.1 constant_pool_count

constant_pool_count(u2):常量池大小,定義了常量池中保存的常量個數(準確說常量個數=constant_pool_count-1)。

0X0013表示constant_pool_count=19,常量池中保存的常量個數=18(編號為#1~#18)。

3.2 constant_pool

constant_pool(constant_pool_count-1個constant_pool_info):實際保存的常量,編號從1開始(將0位留空有特殊考量)。

常量有多種種類,我們這里只提一下我們的Class文件里涉及到的具體的類型。

3.2.1 CONSTANT_Utf8_info

由utf-8編碼的二進制串,其字節碼格式為

類型 名稱 數量
u1 tag 1
u2 length 1
u1 bytes length

其中的tag=0X01即為CONSTANT_Utf8_info類型常量的標識。我們Class字節碼中的#5#6#7#8#9#10#11#12#13#14#17#18都是CONSTANT_Utf8_info常量,因為它們的首位tag=0X01(橘色列),通過utf-8解碼這些常量指定長度的二進制串可以得出下面的結果,比如#5號常量length=1(10進制的0X0001),而bytes為0X6D,utf-8解碼后就是字符串m,同理可以得到這些二進制串的值(這就是javap反編譯出結果的原理,可以參照javap得到的結果對照一下):

#5               m
#6               I
#7               
#8               ()V
#9               Code
#10              LineNumberTable
#11              inc
#12              ()I
#13              SourceFile
#14              Test.java
#17              top/jinhaoplus/demo/Test
#18              java/lang/Object
3.2.2 CONSTANT_Class_info

類常量,其字節碼格式為

類型 名稱 數量
u1 tag 1
u2 index 1

其中的tag=0X07即為CONSTANT_Class_info類型常量的標識,index指向了常量池中類的全限定名的索引序號。

我們Class字節碼中的#3#4CONSTANT_Class_info類型的類常量,它們的首位tag=0X07(橘色列),通過查找常量池中它們指向的索引序號,我們可以得出這兩個類的全限定名:

#3              #17            // top/jinhaoplus/demo/Test
#4              #18            // java/lang/Object
3.2.3 CONSTANT_NameAndType_info

字段或方法的名稱和類型常量,其字節碼格式為

類型 名稱 數量
u1 tag 1
u2 index 1
u2 index 1

其中的tag=0X0C即為CONSTANT_NameAndType_info類型常量的標識,第一個index指向了字段或方法名稱在常量池中的索引序號,第二個index指向了字段或方法的描述符在常量池中的索引序號。

字段的描述符就是簡單的字段類型,Class文件中的類型為了節省空間進行了簡化:如基本類型int->Idouble->D,引用類型java/lang/Object -> Ljava/lang/Object

我們Class字節碼中的#15#16CONSTANT_NameAndType_info類型的類常量,它們的首位tag=0X0C(橘色列),通過查找常量池中它們兩個指向的索引序號,我們可以得出常量#15的名稱為#7號常量即,類型為#8號常量即()V。同理可以得到#16的意思。

#15        #7:#8          // "":()V
#16        #5:#6          // m:I
3.2.4 CONSTANT_Fieldref_info

字段引用常量,其字節碼格式為

類型 名稱 數量
u1 tag 1
u2 index 1
u2 index 1

其中的tag=0X09即為CONSTANT_Fieldref_info類型常量的標識,第一個index指向了聲明字段的類或接口的CONSTANT_Class_info常量在常量池中的索引序號,第二個index指向了字段的名稱和類型信息CONSTANT_NameAndType_info常量在常量池中的索引序號。
我們Class字節碼中的#2CONSTANT_Fieldref_info類型的類常量,它們的首位tag=0X09(橘色列),通過查找常量池中它指向的索引序號,我們可以得出這個字段的聲明類的是top/jinhaoplus/demo/Test,字段的名稱是m,類型是I(即int,Class將類型全稱映射到成了單字母)。

#2           #3.#16         // top/jinhaoplus/demo/Test.m:I
3.2.5 CONSTANT_Methodref_info

方法引用常量,其字節碼格式為

類型 名稱 數量
u1 tag 1
u2 index 1
u2 index 1

其中的tag=0X0A即為CONSTANT_Methodref_info類型常量的標識,第一個index指向了聲明方法的類或接口的CONSTANT_Class_info常量在常量池中的索引序號,第二個index指向了方法的名稱和類型信息CONSTANT_NameAndType_info常量在常量池中的索引序號。
我們Class字節碼中的#1CONSTANT_Fieldref_info類型的類常量,它們的首位tag=0X0A(橘色列),通過查找常量池中它指向的索引序號,我們可以得出這個方法的聲明類是java/lang/Object,方法的名稱是,類型是()V(即無入參返回void類型的方法)。

#1          #4.#15         // java/lang/Object."":()V

至此我們得到了這個Class中的常量池中全部的常量的含義。這些常量將被下面的其他部分引用到。

4.類信息: 4.1 access_flag

access_flag(u2):說明這個類或接口的訪問標志,如private/public/interface/abstract/annotation/enum等,總之是說明了這個類的特征。以不同的特征給出特征位的方式來設置這個u2大小的區域。
如本Class的0X0021實際代表了特征位信息是0000000000110001,即ACC_SUPER|ACC_PUBLIC,表示它是public的class(ACC_SUPER是JDK1.0.2后的默認設置項)。

4.2 this_class

this_class(u2):說明本類的類索引,0X0003說明本類索引在常量池中的序號為3,上面常量池的分析可以看到本類的全限定名是top/jinhaoplus/demo/Test

4.3 super_class

super_class(u2):說明父類的類索引,0X0004說明父類索引在常量池中的序號為4,上面常量池的分析可以看到父類的全限定名是java/lang/Object(這也就是所有Java類的父類都是Object的原因,即使沒有明確寫出來編譯后的Class文件中也會將這個父類聲明定義出來)。

4.4 interface_info
4.4.1 interface_count(u2):說明實現的接口數量,0X0000說明本類沒有實現接口,因此不再有接下來的interface信息。

4.4.2 interface(interface_count個u2):說明接口的類索引。
5.字段信息 5.1 field_count(u2):字段數量

5.2 field_info(field_count個field_info):字段信息,字段表的結構如下:
類型 名稱 數量
u2 access_flag 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

5.2.1 access_flag(u2)用以記錄字段的特征。

比如private/public/protected/static/final/volatile,以不同的特征給出特征位的方式來設置這個u2大小的區域。我們的Class中的0X0001(橙色列)實際代表了特征位信息是0000000000000001,即字段的特征是ACC_PUBLIC(public字段)。

5.2.2 name_index(u2)是字段名在常量池中的索引序號。

我們的Class中的0X0005(藍色列)指向的常量池中的#5號常量即m

5.2.3 descriptor_index(u2)是字段描述符在常量池中的索引序號。

我們的Class中的0X0006(青色列)指向的常量池中的#6號常量即I

5.2.4 字段的屬性表是本字段的屬性表:

5.2.4.1 attributes_count(u2):字段屬性表的屬性數量,我們的Class中的0X0000表示本字段無額外的屬性表信息。

5.2.4.1 attributes(attributes_count個attribute_info):字段屬性表的屬性信息,字段屬性有自己定義的結構,字段中主要使用的屬性包括ConstantValue(final修飾的常量值作為字段的值)、Depreciated(@Depreciated修飾的字段表示棄用)、Signature(泛型參數記錄的泛型簽名信息,否則編譯后擦除類型就無法溯源了)等,他們都有各自定義的結構。

6.方法信息 6.1 method_count

method_count(u2):方法數量
我們的Class這個區的0X0002表示這個類有兩個方法。

6.2 method_info

method_info(method_count個method_info):方法信息,方法表的結構如下:

類型 名稱 數量
u2 access_flag 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

6.2.1 access_flag(u2)用以記錄方法的特征。

比如private/public/protected/static/final/synchronized,以不同的特征給出特征位的方式來設置這個u2大小的區域。我們的Class中兩個方法的這個區域的0X0001(橙色列)實際代表了特征位信息是0000000000000001,即它們的特征都是ACC_PUBLIC(public方法)。

6.2.2 name_index(u2)是方法名在常量池中的索引序號。

我們的Class中,method_#10X0005(藍色列)指向的常量池中的#7號常量即,而。method_#20X000B(藍色列)指向的常量池中的#11號常量即inc

6.2.3 descriptor_index(u2)是方法描述符在常量池中的索引序號。

我們的Class中,method_#10X0008(青色列)指向的常量池中的#8號常量即()V,而。method_#20X000C(青色列)指向的常量池中的#12號常量即()I

6.2.4 字段的屬性表是該方法的屬性表:

6.2.4.1 attributes_count(u2):方法屬性表的屬性數量。

6.2.4.2 attributes(attributes_count個attribute_info):方法屬性表的屬性信息,方法屬性有自己定義的結構,方法中主要使用的屬性包括最重要的Code(方法的字節碼指令,沒有方法執行體的接口和抽象類是沒有這個屬性的)、Exceptions(聲明方法拋出的異常)、Depreciated(@Depreciated修飾的方法表示棄用)、Signature(泛型參數記錄的泛型簽名信息)等,他們都有各自定義的結構。這里我們具體來看一下最重要的Code屬性。

類型 名稱 數量
u2 attribute_name_index 1
u4 attribute_length 1
u2 max_stack 1
u2 max_locals 1
u4 code_length 1
u1 code code_length
u2 exception_table_length 1
exception_info exception_table exception_table_length
u2 attribute_count 1
attribute_info attributes attribute_count

a. attribute_name_index(u2):屬性名在常量池中的索引序號,Code屬性最終找到的常量肯定是Code

b. attribute_length(u4):該屬性的長度。

c. max_stack(u2):該方法的操作數棧最大深度。

d. max_locals(u2):該方法的局部變量表的大小。

e. code_length(u4):字節碼指令的大小

f. code(exception_table_length個u1):字節碼。

g. exception_table_length(u2):異常表大小。

h. exception_table(exception_table_length個exception_info):異常表大小。

i. attributes_count(u2):方法屬性表的大小。

j. attributes(attribute_count個attribute_info):方法屬性表。

接下來用我們Class的兩個方法來詳細說明Code屬性:

method_#1方法:

i. attributes_count = 1

ii. attributes:

a. attribute_name_index:常量0X0009Code

b. attribute_length:29(0X0000001D),即下一位起后的29u都是這個屬性。

c. max_stack:1(0X0001)。

d. max_locals:1(0X0001)。

e. code_length:5(0X00000005)。

f. code:0X2AB70001B1。(字節碼指令的具體含義鑒于與class結構是相對獨立的主題不再詳述,后續會再多帶帶深入介紹)

g. exception_table_length:0(OX0000)。

h. exception_table:無。

i. attributes_count:1(0X0001)。

j. attributes:

attribute_name_index:LineNumberTable(0X000A),說明這是一個用于記錄源碼行號和字節碼行號映射的屬性表。

attribute_length:6(0X00000006).

attribute:LineNumberTable屬性表的內部結構:

line_number_table_length:1(0X0001)。

line_number_index:0:3(0X00000003)。

method_#2方法的分析方式如上類似不再贅述。

Class字節碼的結構為什么這么設計

乍一看來上面的結構讓人很難快速理解,但是如果理解JVM的字節碼結構的設計目的就可以加深理解了。

JVM的字節碼結構其實是一種由字節碼堆砌的表型結構,充分定義占位的結構可以無歧義地將它想要表達的原義還原回去。作為二進制結構主要的表達方式,只要定義好占位情況,表型結構可以通過層層嵌套定義來實現更為復雜的結構、并且可以實現良好的拓展。

比如上面的介紹的方法信息通過方法數量定義了這個表的大小,而每個表entry內部可以再有自己的定義,比如方法信息中還可以包含屬性表(即在方法表內部再嵌套一層表),比如這里定義了Code屬性表,而Code屬性表自身又有良好的表結構定義,這個表內部除了一些一維的字段(比如index、count等不能拓展的字段)外,還有額外的exception_table,但是因為有exception_table_length的表大小限制就可以無歧義地還原回去,此外還有attribute_info,但是因為有attribute_count的表大小限制也可以無歧義地還原回去。用下面的思維導圖我們可以直觀地看出來這種良好的定義,圖中加入了每個一維節點的占位大小:

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76736.html

相關文章

  • Java經典

    摘要:請注意,我們在聊聊單元測試遇到問題多思考多查閱多驗證,方能有所得,再勤快點樂于分享,才能寫出好文章。單元測試是指對軟件中的最小可測試單元進行檢查和驗證。 JAVA容器-自問自答學HashMap 這次我和大家一起學習HashMap,HashMap我們在工作中經常會使用,而且面試中也很頻繁會問到,因為它里面蘊含著很多知識點,可以很好的考察個人基礎。但一個這么重要的東西,我為什么沒有在一開始...

    xcold 評論0 收藏0
  • 【備戰春招/秋招系列】美團面經總結基礎篇 (附詳解答案)

    摘要:不同于個人面經,這份面經具有普適性。我在前面的文章中也提到了應該怎么做自我介紹與項目介紹,詳情可以查看這篇文章備戰春招秋招系列初出茅廬的程序員該如何準備面試。是建立連接時使用的握手信號。它表示確認發來的數據已經接受無誤。 showImg(https://segmentfault.com/img/remote/1460000016972448?w=921&h=532); 該文已加入開源文...

    Leck1e 評論0 收藏0
  • 后端知識拓展 - 收藏集 - 掘金

    摘要:阻塞,非阻塞首先,阻塞這個詞來自操作系統的線程進程的狀態模型網絡爬蟲基本原理一后端掘金網絡爬蟲是捜索引擎抓取系統的重要組成部分。每門主要編程語言現未來已到后端掘金使用和在相同環境各加載多張小圖片,性能相差一倍。 2016 年度小結(服務器端方向)| 掘金技術征文 - 后端 - 掘金今年年初我花了三個月的業余時間用 Laravel 開發了一個項目,在此之前,除了去年換工作準備面試時,我并...

    CoderBear 評論0 收藏0
  • 后端知識拓展 - 收藏集 - 掘金

    摘要:阻塞,非阻塞首先,阻塞這個詞來自操作系統的線程進程的狀態模型網絡爬蟲基本原理一后端掘金網絡爬蟲是捜索引擎抓取系統的重要組成部分。每門主要編程語言現未來已到后端掘金使用和在相同環境各加載多張小圖片,性能相差一倍。 2016 年度小結(服務器端方向)| 掘金技術征文 - 后端 - 掘金今年年初我花了三個月的業余時間用 Laravel 開發了一個項目,在此之前,除了去年換工作準備面試時,我并...

    Carl 評論0 收藏0
  • Java編程基礎01——計算機基礎

    摘要:外部存儲器可用于長期保存大量程序和數據,其成本低容量大,但速度較慢。 1_計算機概述(了解) A:什么是計算機?計算機在生活中的應用舉例 計算機(Computer)全稱:電子計算機,俗稱電腦。是一種能夠按照程序運行,自動、高速處理海量數據的現代化智能電子設備。由硬件和軟件所組成,沒有安裝任何軟件的計算機稱為裸機。常見的形式有臺式計算機、筆記本計算機、大型計算機等。 應用舉例 ...

    xiangzhihong 評論0 收藏0

發表評論

0條評論

ygyooo

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<