認(rèn)識(shí) .class 文件的字節(jié)碼結(jié)構(gòu)

在說道 JVM 虛擬機(jī)的時(shí)候,很多人都會(huì)想到 Java 語言,誠然,Java 語言和 JVM 虛擬機(jī)息息相關(guān),但是 .class 文件與 JVM 虛擬機(jī)的關(guān)系比 Java 語言和虛擬機(jī)的關(guān)系還要親密。為什么這樣說呢?因?yàn)?Java 語言經(jīng)過編譯器編譯之后生成的 .class 文件才是真正運(yùn)行在 JVM 虛擬機(jī)中的文件,而不是 .java 文件。
經(jīng)過這么多年的發(fā)展,可以以 JVM 虛擬機(jī)為平臺(tái)運(yùn)行的語言,不止 Java 語言,包括 Kotlin、Groovy、Scala 等語言現(xiàn)在都是運(yùn)行在 JVM 虛擬機(jī)上的語言,而且這些語言都是通過編譯之后生成 .class 文件之后,再運(yùn)行在 JVM 虛擬機(jī)上的,比起 .java、.groovy 等文件,.class 文件對(duì)于 JVM 虛擬機(jī)更加重要。如果足夠牛逼,寫一個(gè)編譯器將 .c 文件編譯成 .class 文件,運(yùn)行在 JVM 虛擬機(jī)上也是可以的。其實(shí)這也就是所謂的 Java 虛擬機(jī)的 “語言無關(guān)性”
本篇文章將介紹 .class 文件的結(jié)構(gòu),通過一個(gè)簡(jiǎn)單的例子認(rèn)識(shí) .class 文件。


catalog.png

一. 簡(jiǎn)介

寫一個(gè)簡(jiǎn)單的 Demo.java 程序如下所示

package com.lijiankun24.classpractice;

public class Demo {

    private int m;

    public int inc() {
        return m + 1;
    }
}

使用 javac 命令編譯 Demo.java 文件生成 Demo.class 文件

$ javac Demo.java

接著用文本編輯器打開生成的 Demo.class 文件,如下所示

cafe babe 0000 0034 0013 0a00 0400 0f09
0003 0010 0700 1107 0012 0100 016d 0100
0149 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 0369 6e63
0100 0328 2949 0100 0a53 6f75 7263 6546
696c 6501 0009 4465 6d6f 2e6a 6176 610c
0007 0008 0c00 0500 0601 0004 4465 6d6f
0100 106a 6176 612f 6c61 6e67 2f4f 626a
6563 7400 2100 0300 0400 0000 0100 0200
0500 0600 0000 0200 0100 0700 0800 0100
0900 0000 1d00 0100 0100 0000 052a b700
01b1 0000 0001 000a 0000 0006 0001 0000
0001 0001 000b 000c 0001 0009 0000 001f
0002 0001 0000 0007 2ab4 0002 0460 ac00
0000 0100 0a00 0000 0600 0100 0000 0600
0100 0d00 0000 0200 0e

可以看到,該文件中是由十六進(jìn)制符號(hào)組成的,這一段十六進(jìn)制符號(hào)組成的長(zhǎng)串是遵守 Java 虛擬機(jī)規(guī)范的

二. Java 虛擬機(jī)規(guī)范

在 Java 虛擬機(jī)規(guī)范中規(guī)定了 Java 虛擬機(jī)結(jié)構(gòu)、Class 類文件結(jié)構(gòu)、字節(jié)碼指令等內(nèi)容,可以參考 GitHub 上的《Java 虛擬機(jī)規(guī)范》

2.1 Java 虛擬機(jī)

  1. 可以說 Java 虛擬機(jī)有兩大特性:平臺(tái)無關(guān)性和語言無關(guān)性,本篇文章主要介紹語言無關(guān)性的重要知識(shí):.class 文件結(jié)構(gòu)
  2. Java 虛擬機(jī)就是一個(gè)虛擬的計(jì)算機(jī),與真實(shí)的計(jì)算機(jī)一樣,Java 虛擬機(jī)有自己完善的硬件體系,如處理器、堆棧、寄存器,還有相應(yīng)的指令集系統(tǒng)。虛擬機(jī)與真實(shí)電腦的唯一區(qū)別就是:虛擬機(jī)的處理器、內(nèi)存堆棧是用軟件虛擬出來的,而真實(shí)的電腦的處理器、內(nèi)存則是真真實(shí)實(shí)存在的
  3. 在 Java 虛擬機(jī)規(guī)范中,介紹的 Java 虛擬機(jī)的整體架構(gòu)、Java 虛擬機(jī)內(nèi)存區(qū)域、垃圾回收、.class 文件結(jié)構(gòu)、類加載機(jī)制和 Java 虛擬機(jī)指令集。在本篇文章中主要介紹 .class 文件結(jié)構(gòu),其他內(nèi)容可以查閱相關(guān)書籍和 Java 虛擬機(jī)規(guī)范

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

.class 文件是一組以 8 位字節(jié)為基礎(chǔ)單位的二進(jìn)制流,各數(shù)據(jù)項(xiàng)目嚴(yán)格按照順序緊湊地排列在 .class 文件中,中間沒有添加任何分隔符,這使得整個(gè) .class 文件中存儲(chǔ)的內(nèi)容幾乎全都是程序需要的數(shù)據(jù),沒有空隙存在

  1. .class 文件是以類似于 C 語言結(jié)構(gòu)體的結(jié)構(gòu)來存儲(chǔ)數(shù)據(jù)的,其中存儲(chǔ)的數(shù)據(jù)有兩種:無符號(hào)數(shù)和表

  2. 無符號(hào)數(shù)屬于最基本的數(shù)據(jù)類型,以 u1、u2、u4、u8 分別代碼 1 個(gè)字節(jié)、2 個(gè)字節(jié)、4 個(gè)字節(jié)和 8 個(gè)字節(jié)的無符號(hào)數(shù),無符號(hào)數(shù)可以用來描述數(shù)字、索引引用、數(shù)量值或者按照 UTF-8 編碼構(gòu)成的字符串值

  3. 表是一種復(fù)合數(shù)據(jù)結(jié)構(gòu),由無符號(hào)數(shù)或其他表構(gòu)成,所有表都習(xí)慣性地以 “info” 結(jié)尾

  4. 在 .class 中有一個(gè)集合的概念。集合表示同一類數(shù)據(jù)項(xiàng)的集合,一般是由一個(gè)前置的計(jì)數(shù)器加若干個(gè)連續(xù)的同樣類型的數(shù)據(jù)項(xiàng)組成,計(jì)數(shù)器表示此集合中數(shù)據(jù)項(xiàng)的個(gè)數(shù),數(shù)據(jù)項(xiàng)是真正的數(shù)據(jù)內(nèi)容

  5. 整個(gè) .class 文件本質(zhì)上就是一張表,由下表所示的數(shù)據(jù)項(xiàng)構(gòu)成


    class.png
  6. 上面的表其實(shí)可以劃分為以下七個(gè)部分,.class 字節(jié)碼文件包括:

  • 魔數(shù)與class文件版本
  • 常量池
  • 訪問標(biāo)志
  • 類索引、父類索引、接口索引
  • 字段表集合
  • 方法表集合
  • 屬性表集合

三. class 文件詳解

我們通過 Demo.class 為例講解 .class 文件的 7 個(gè)部分

3.1 魔數(shù)和 class 文件版本

3.1.1 概念介紹

在魔數(shù)和 class 文件版本中有如下四點(diǎn)需要介紹:

  1. 魔數(shù)(Magic Number):.class 文件的第 1 - 4 個(gè)字節(jié),它唯一的作用就是確定這個(gè)文件是否是一個(gè)能被虛擬機(jī)接受的 class 文件,其固定值是:0xCAFEBABE(咖啡寶貝)。如果一個(gè) class 文件的魔術(shù)不是 0xCAFEBABE,那么虛擬機(jī)將拒絕運(yùn)行這個(gè)文件
  2. 次版本號(hào)(minor version):.class 文件的第 5 - 6 個(gè)字節(jié),即編譯生成該 .class 文件的 JDK 次版本號(hào)
  3. 主版本號(hào)(major version):.class 文件的第 7 - 8個(gè)字節(jié),即編譯生成該 .class 文件的 JDK 主版本號(hào)
  4. Note:高版本的 JDK 能向下兼容低版本的 .class 文件,但不能運(yùn)行新版本的 .class 文件。例如一個(gè) .class 文件是使用 JDK 1.5 編譯的,那么我們可以用 JDK 1.7 虛擬機(jī)運(yùn)行它,但不能用 JDK 1.4 虛擬機(jī)運(yùn)行它。各個(gè)版本的 SDK 的次版本號(hào)和主版本號(hào)如下表所示
    sdk.png
3.1.2 示例

在上面的 Demo.class 文件中,Magic Number:0xcafe babe,minor version:0x0000,major version:0x0034,可見我們是使用 JDK 1.8 編譯生成的 Demo.class 文件

3.2 常量池

3.2.1 概念介紹

緊接著版本號(hào)之后的是常量池的入口,常量池可以理解為 class 文件之中的資源倉庫,它是占用 class 文件空間最大的數(shù)據(jù)項(xiàng)之一。

常量池是一個(gè)集合,它由兩部分組成:常量池計(jì)數(shù)器和常量池

  1. 常量池計(jì)數(shù)器(constant_pool_count) 是一個(gè) u2 的無符號(hào)數(shù)
  2. 常量池(constant_pool):緊跟在常量池計(jì)數(shù)器后面的內(nèi)容就是該 .class 文件的常量池內(nèi)容了,常量池中存放的數(shù)據(jù)一般分為兩種類型:字面量和符號(hào)引用
    • 字面量:是指文本字符串、聲明為 final 的常量值等
    • 符號(hào)引用: 是一個(gè)更偏向于編譯原理方面的概念,主要包括三類常量:1). 類和接口的全限定名,2).字段的名稱和描述符,3). 方法的名稱和描述符
  3. 在常量池中的常量共有 14 種類型,每個(gè)常量都是一個(gè)表,每一個(gè)表都有各自的組成結(jié)構(gòu)。這 14 個(gè)常量有一個(gè)公共的特點(diǎn),就是每個(gè)常量開始是一個(gè)用 u1 類型的無符號(hào)數(shù)表示的標(biāo)志位(tag,取值見下表),表示此常量屬于哪種常量類型


    cp_info.png
3.2.2 示例

在上面的 Demo.class 文件中,常量池開始的偏移地址是:0x0008。

  1. 首先是常量計(jì)數(shù)器(constant_pool_coun),數(shù)值是:0x0013,表示此 Demo.class 文件中共有 18 個(gè)常量
  2. cp_info_constant_pool[1]:偏移地址是 0x000A,內(nèi)容是:0x0A0004000F。0x0A 標(biāo)志位表示是一個(gè) CONSTANT_Methodref_info 常量,0x0004 是一個(gè)索引,指向常量池中第 4 個(gè)常量所表示的信息;0x000F 是一個(gè)索引,指向常量池第 15 個(gè)常量所表示的信息。CONSTANT_Methodref_info 常量的結(jié)構(gòu)如下所示:
    tag index index
    u1 u2 u2
    10 索引項(xiàng):指向聲明方法的類描述符 CONSTANT_Class_info 索引項(xiàng):指向名稱及類型描述符 CONSTANT_NameAndType
  3. cp_info_constant_pool[2]:偏移地址是0x000F,內(nèi)容是:0x0900030010,0x09 表示此常量是一個(gè) CONSTANT_Fieldref_info 常量,0x0003 表示一個(gè)索引,指向常量池第 3 個(gè)常量所表示的信息;0x0010 是一個(gè)索引,表示指向常量池第 16 個(gè)常量所表示的信息。CONSTANT_Fieldref_info 常量的結(jié)構(gòu)如下所示:
    tag index index
    u1 u2 u2
    9 索引項(xiàng):指向聲明字段的類描述符 CONSTANT_Class_info 索引項(xiàng):指向字段描述符 CONSTANT_NameAndType
  4. cp_info_constant_pool[3]:偏移地址是0x0014,內(nèi)容是:0x070011。0x07 標(biāo)志位表示此常量是一個(gè) CONSTANT_Class_info 常量,索引 0x0011 指向常量池中第 17 個(gè)常量。CONSTANT_Class_info 常量的結(jié)構(gòu)如下所示:
    tag index
    u1 u2
    7 索引項(xiàng):指向全限定名常量項(xiàng)的索引
  5. cp_info_constant_pool[4]:偏移地址是0x0017,內(nèi)容是:0x070012。0x07 標(biāo)志位表示此常量是一個(gè) CONSTANT_Class_info 常量,索引 0x0012 指向常量池中第 18 個(gè)常量。
  6. cp_info_constant_pool[5]:偏移地址是0x001A,內(nèi)容是:0x0100016D。0x01 表示此常量是一個(gè) CONSTANT_Utf8_info 常量,0x0001 表示 UTF-8 編碼的字符串占用的字節(jié)數(shù);0x6D 表示 長(zhǎng)度為 1 的 UTF-8 編碼的字符串的內(nèi)容: m。CONSTANT_Utf8_info 常量的結(jié)構(gòu)如下所示:
    tag length bytes
    u1 u2 u1
    1 Utf-8 編碼的字符串占用的字節(jié)數(shù) length 長(zhǎng)度的 UTF-8 編碼的字符串內(nèi)容
  7. cp_info_constant_pool[6]:偏移地址是0x001E,內(nèi)容是:0x01000149。0x01 表示此常量是一個(gè) CONSTANT_Utf8_info 常量,0x0001 表示 UTF-8 編碼的字符串占用的字節(jié)數(shù);0x49 表示長(zhǎng)度為 1 的 UTF-8 編碼的字符串的內(nèi)容: I。
  8. cp_info_constant_pool[7]:偏移地址是0x0022,內(nèi)容是:0x0100063C696E69743E。0x01 表示此常量是一個(gè) CONSTANT_Utf8_info 常量,0x0006 表示字符串長(zhǎng)度為 6,0x3C696E69743E 表示長(zhǎng)度為 6 的 UTF-8 編碼的字符串的內(nèi)容: <init>。

上面分析了 7 個(gè)常量,其余的常量也是類似的方法。根據(jù)第一個(gè) u1 的標(biāo)志位,就知道這個(gè)常量的類型和表結(jié)構(gòu),就可以知道這個(gè)常量的長(zhǎng)度大小和代表的含義了。我們也可以通過 “javap -verbose” 命令查看 .class 文件的內(nèi)容,如下圖所示:


constant_pool.png

3.3 訪問標(biāo)志

3.3.1 概念介紹

常量池之后是 u2 類型的訪問標(biāo)志位(access_flags),這個(gè)訪問標(biāo)志位用于標(biāo)識(shí)類或者接口層次的訪問信息,包括:這個(gè) Class 是類還是接口、是否定義為public類型、是否定義為abstract類型,如果是類的話,是否被 final 關(guān)鍵字修飾。具體的標(biāo)志位以及標(biāo)志的含義見下表


access_flags.png
3.3.2 示例

在 Demo.class 文件中訪問標(biāo)志位是:0x0021。在上表中,我們并沒有發(fā)現(xiàn) 00 21 的訪問標(biāo)志,這是因?yàn)樵谧止?jié)碼文件中的訪問標(biāo)志,可以通過上表中多個(gè)訪問標(biāo)志通過或運(yùn)算組成真正的訪問標(biāo)志。通過上表中的 ACC_SUPER 和 ACC_PUBLIC 就可以組合中 00 21 的訪問標(biāo)志了,也就是說該類的訪問標(biāo)志是 public 且允許使用 invokespecial 字節(jié)碼指令的新語義的

3.4 類索引、父類索引、接口索引

3.4.1 概念介紹

在 .class 文件中由這三項(xiàng)數(shù)據(jù)來確定這個(gè)類的繼承關(guān)系。

  1. 類索引:u2 數(shù)據(jù)類型,用于確定這個(gè)類的全限定名。
  2. 父類索引:u2 數(shù)據(jù)類型,用于確定這個(gè)類的父類的全限定名。
  3. 接口索引:u2 數(shù)據(jù)類型的集合,用于描述類實(shí)現(xiàn)了哪些接口,這些被實(shí)現(xiàn)的接口將按照 implements 語句后的順序從左至右排列在接口索引集合中。接口索引集合分為兩部分,第一部分表示接口計(jì)數(shù)器(interfaces_count),是一個(gè) u2 類型的數(shù)據(jù),第二部分是接口索引表表示接口信息,緊跟在接口計(jì)數(shù)器之后。若一個(gè)類實(shí)現(xiàn)的接口為 0,則接口計(jì)數(shù)器的值為 0,接口索引表不占用任何字節(jié)。
3.4.2 示例

在此 Demo.class 文件中,類索引、父類索引、接口索引分別如下:

  1. 類索引:偏移地址是 0x00B3,內(nèi)容是 0x0003,表示其指向了常量池中第 3 個(gè)常量 CONSTANT_Class_info,第 3 個(gè)常量索引指向第 17 個(gè)常量,第 17 個(gè)常量是一個(gè) UTF-8 編碼的字符串,其值是:com/lijiankun24/classpractice/Demo,表示此類的全限定名
  2. 父類索引:偏移地址是 0x00B5,內(nèi)容是 0x0004,其指向了常量池中第 4 個(gè)常量 CONSTANT_Class_info,第 4 個(gè)常量索引指向第 18 個(gè)常量,第 18 個(gè)常量的值是:java/lang/Object,表示父類的全限定名
  3. 接口索引:偏移地址是 0x00B7,內(nèi)容是 0x0000。因?yàn)?Demo 類沒有實(shí)現(xiàn)任何接口,所以接口索引的計(jì)數(shù)器是 0,表示沒有接口索引。

3.5 字段表集合

3.5.1 概念介紹

字段表集合用于描述接口或類中聲明的變量。這里說的字段包括類級(jí)變量(static 修飾)和對(duì)象級(jí)變量(沒有用 static 修飾),但不包括方法中聲明的局部變量。

字段表集合包括兩部分:字段計(jì)數(shù)器和字段表,字段計(jì)數(shù)器表示有多少個(gè)字段,字段表的每個(gè)字段用一個(gè)名為 field_info 的表來表示,field_info 表的數(shù)據(jù)結(jié)構(gòu)如下所示:


field_info.png

字段表都包含的固定數(shù)據(jù)項(xiàng)目到 descriptor_index 為止就結(jié)束了,不過在 descriptor_index 之后跟隨著一個(gè)屬性表集合用于存儲(chǔ)一些額外的信息,字段都可以在屬性表中描述零至多項(xiàng)的額外信息。在字段描述符之后,一般會(huì)有該字段的屬性表集合,屬性表集合有兩部分,第一部分是屬性計(jì)數(shù)器,第二部分是屬性表。
在字段表集合中不會(huì)列出父類的字段,但是有可能會(huì)有一些 Java 代碼中沒有聲明的字段,比如在內(nèi)部類中為了保持對(duì)外部類的訪問性,會(huì)自動(dòng)添加指向外部類實(shí)例的字段

3.5.2 示例

在 Demo.class 文件中的字段表集合的偏移地址是:0x00B9,內(nèi)容是:0x 0001 0002 0005 0006 0000.

  1. 0x0001 表示字段計(jì)數(shù)器是 1,表示只有 1 個(gè)字段
  2. field_info_fields[0]:偏移地址是 0x00BB,內(nèi)容是 0x0002 0005 0006 0000,根據(jù)字段表的結(jié)構(gòu)來分析這段數(shù)據(jù)
    • 0x0002 表示該字段的訪問標(biāo)識(shí),0002 表示是 private 的
    • 0x0005 表示該字段的名稱索引項(xiàng),指向常量池中的第 5 個(gè)常量,第 5 個(gè)常量是一個(gè) UTF-8 的字符串 m
    • 0x0006 表示該字段的描述符索引項(xiàng),指向常量池中的第 6 個(gè)常量,第 6 個(gè)常量是一個(gè) UTF-8 的字符串 I,I 描述符表示是一個(gè) int 類型的字段
    • 0x0000 表示 m 字段的屬性表集合,屬性表集合計(jì)數(shù)器是 0,表示此字段沒有額外的屬性信息。

3.6 方法表集合

3.6.1 概念介紹

在字段表之后緊跟著方法表集合,方法表表示類或接口中的方法信息。

方法表集合和上述的字段表集合幾乎完全一樣,最開始的 2 個(gè)字節(jié)表示一個(gè)方法計(jì)數(shù)器,在方法計(jì)數(shù)器之后,才是真正的方法數(shù)據(jù)項(xiàng)。方法表中的每個(gè)方法都用一個(gè) method_info 表示,其數(shù)據(jù)結(jié)構(gòu)如下:


method_info.png

在方法表結(jié)構(gòu)中,我們可以看到方法的訪問標(biāo)志位、名稱索引、描述符索引、屬性表集合,方法中的代碼在編譯之后,放到方法屬性表集合中的一個(gè)名為 “code” 的屬性里面

3.6.2 示例
  1. 在 Demo.class 中方法表集合的偏移地址是:0x00C3,方法表集合計(jì)數(shù)器是 0x0002, 表示此方法表集合中有兩個(gè)方法表數(shù)據(jù)項(xiàng)??赡苡腥藭?huì)有疑問,Demo.java 中我們只寫了一個(gè)方法,為什么在方法表中會(huì)有兩個(gè)方法呢?因?yàn)榫幾g器會(huì)自動(dòng)添加實(shí)例構(gòu)造器 <init> 方法
  2. method_info_methods[0]:偏移地址是:0x00C5,內(nèi)容是:0x00 0100 0700 0800 0100 0900 0000 1d00 0100 0100 0000 052a b700 01b1 0000 0001 000a 0000 0006 0001 0000 0003。
    • 0x0001:access_flags 表示ACC_PUBLIC,即表示該方法是 public 的

    • 0x0007:name_index 表示方法名稱索引,指向常量池中的第 7 個(gè)常量,第 7 個(gè)常量是一個(gè) UTF-8 字符串,值是:<init>

    • 0x0008:descriptor_index 表方法描述符索引項(xiàng),指向常量池中的第 8 個(gè)常量,是一個(gè) UTF-8 字符串,值是:()V

    • 0x0001:表示此方法的屬性表集合計(jì)數(shù)器,有 1 個(gè)屬性

    • 0x0009:表示此屬性的 attribute_name_index,指向常量池中的第 9 UTF-8 常量:Code,說明此屬性是方法的字節(jié)碼描述 Code 屬性


      Code.png

      那么我們就依次按照上表的結(jié)構(gòu)分析此實(shí)例構(gòu)造器的字節(jié)碼內(nèi)容,至于具體的字節(jié)碼含義會(huì)在后面的文章中分析介紹

    • 0x0009:上面已經(jīng)介紹過,表示此 Code 屬性的名稱索引,其值就是 “Code” 字符串

    • 0x0000 001d:attribute_length 表示屬性長(zhǎng)度為 29 個(gè)字節(jié)

    • 0x0001:max_stack 表示操作數(shù)棧的最大深度是 1

    • 0x0001:max_locals 表示局部變量表的最大長(zhǎng)度是 1

    • 0x0000 0005:code_length 表示字節(jié)碼指令長(zhǎng)度是 5,共有 5 個(gè)字節(jié)碼指令

    • 0x2ab7 0001 b1:這 5 個(gè) u1 數(shù)據(jù),表示 3 個(gè)字節(jié)碼指令,0x2a = aload_0,0xb7 = invokespecial,0x0001 = 表示一個(gè)指向常量池的索引,是 invokespecial 指令的參數(shù),0xb1 = return 表示從當(dāng)前方法返回

    • 0x0000 exception_table_length=0,異常表集合長(zhǎng)度為 0

    • 0x0001:attributes_count=1(Code屬性表內(nèi)部還含有1個(gè)屬性表)

    • 0x000a:指向常量池中的第十個(gè)常量:LineNumberTable,LineNumberTable 屬性結(jié)構(gòu)如下圖所示,內(nèi)容是:0000 0006 0001 0000 0003


      LineNumberTable.jpeg
    • 0x0000 0006:attribute_length 表示屬性長(zhǎng)度為 6

    • 0x0001:line_number_table_length,表示后面的 line_number_info 表有 1 個(gè),line_number_info表包括了 start_pc 和 line_number 兩個(gè) u2 類型的數(shù)據(jù)項(xiàng),前者是字節(jié)碼行號(hào),后者是 java 源碼行號(hào):start_pc:00 00,end_pc:00 03

  3. method_info_methods[1]:上面我們分析了第一個(gè)方法:實(shí)例構(gòu)造器方法,分析流程就是上面這樣,方法表有固定的結(jié)構(gòu),其中包含一些固定的信息,包括操作數(shù)棧最大深度、局部變量表最大長(zhǎng)度、以及很重要的 Code 屬性,在 Code 屬性中包含 java 方法編譯生成的字節(jié)碼指令,如果想快速的瀏覽方法表集合的內(nèi)容,也可以使用 "javap -verbose Demo.class" 指令查看,如下圖所示


    Demo.png

3.7 屬性表集合

3.7.1 概念介紹
  1. 在 class 文件、字段表、方法表都可以攜帶自己的屬性表集合,用以描述某些場(chǎng)景專有的信息。
  2. 屬性表的格式是相對(duì)固定的,包括三部分內(nèi)容:
    • 一個(gè) u2 的 attribute_name_index 指向常量池中的一個(gè) UTF-8 字符串常量表示一個(gè)屬性名稱
    • 一個(gè) u4 的數(shù)據(jù)類型表示 attribute_length 表示該屬性值的字節(jié)長(zhǎng)度
    • 該長(zhǎng)度的屬性值信息,結(jié)構(gòu)如下圖所示:


      attribute.png
  3. 對(duì)于屬性表的限制來說相對(duì)較寬松,任何人實(shí)現(xiàn)的編譯器都可以向?qū)傩员碇袑懭胱远x的屬性值信息,Java 虛擬機(jī)對(duì)于它自己不認(rèn)識(shí)的屬性值則會(huì)忽略掉。
  4. 在 Java 7 虛擬機(jī)規(guī)范中已經(jīng)預(yù)定義了 21 項(xiàng)屬性
3.7.2 示例
  1. Demo.class 中屬性表的偏移地址是:0x011D,內(nèi)容是 0x00 0100
    0D00 0000 0200 0E
  2. 0x0001 表示此屬性表集合的計(jì)數(shù)器是1,有 1 個(gè)屬性
  3. attribute_info_attributes[0]:偏移地址是:0x011F,內(nèi)容是 0x00 0D00 0000 0200 0E
    • 0x000D:指向常量池中的第 13 個(gè) Utf-8 常量:SourceFile,SourceFile 屬性用于記錄生成這個(gè) Class 文件的源碼文件名稱,其結(jié)構(gòu)如下圖所示:


      SourceFile.png
    • 0x0000 0002:attribute_length 屬性長(zhǎng)度是 2

    • 00 0E:sourcefile_index 指向常量池中第 14 個(gè)常量 Demo.java

3.8 010 Editor

分析 .class 文件結(jié)構(gòu)是比較枯燥無聊的,但是如果可以看懂 .class 文件結(jié)構(gòu)的內(nèi)容,并且理解其中的含義,知道 .class 文件結(jié)構(gòu)中 Code 屬性中字節(jié)碼指令的執(zhí)行過程,對(duì)我們的 Java 能力提升還是比較大的。
分析 .class 文件結(jié)構(gòu),我們可以使用 "javap -verbose Demo.class" 指令查看,我們也可以使用 010 Editor 軟件分析,可以方便的查看各個(gè)數(shù)據(jù)項(xiàng)的地址偏移量、數(shù)據(jù)項(xiàng)內(nèi)容。比如,我們想查看第 4 個(gè)常量池的內(nèi)容,如下圖所示


010Eidtor.png

寫在最后,這篇文章分析了 .class 文件的結(jié)構(gòu),知道了其本質(zhì)是以 8 位字節(jié)為單位存儲(chǔ)的二進(jìn)制流文件,那虛擬機(jī)是如何加載并分析執(zhí)行其中的內(nèi)容的呢?這些內(nèi)容我將在后面的文章中介紹,敬請(qǐng)期待。

最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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