1. 程式人生 > 其它 >class檔案結構

class檔案結構

參考資料:尚矽谷JVM教程

15.class檔案結構

15.1. Class位元組碼檔案結構

型別 名稱 說明 長度 數量
魔數 u4 magic 魔數,識別Class檔案格式 4個位元組 1
版本號 u2 minor_version 副版本號(小版本) 2個位元組 1
u2 major_version 主版本號(大版本) 2個位元組 1
常量池集合 u2 constant_pool_count 常量池計數器 2個位元組 1
cp_info constant_pool 常量池表 n個位元組 constant_pool_count - 1
訪問標識 u2 access_flags 訪問標識 2個位元組 1
索引集合 u2 this_class 類索引 2個位元組 1
u2 super_class 父類索引 2個位元組 1
u2 interfaces_count 介面計數器 2個位元組 1
u2 interfaces 介面索引集合 2個位元組 interfaces_count
欄位表集合 u2 fields_count 欄位計數器 2個位元組 1
field_info fields 欄位表 n個位元組 fields_count
方法表集合 u2 methods_count 方法計數器 2個位元組 1
method_info methods 方法表 n個位元組 methods_count
屬性表集合 u2 attributes_count 屬性計數器 2個位元組 1
attribute_info attributes 屬性表 n個位元組 attributes_count

15.2. Class檔案資料型別

資料型別 定義 說明
無符號數 無符號數可以用來描述數字、索引引用、數量值或按照utf-8編碼構成的字串值。 其中無符號數屬於基本的資料型別。 以u1、u2、u4、u8來分別代表1個位元組、2個位元組、4個位元組和8個位元組
表是由多個無符號數或其他表構成的複合資料結構。 所有的表都以“_info”結尾。 由於表沒有固定長度,所以通常會在其前面加上個數說明。

15.3. 魔數

Magic Number(魔數)

  • 每個Class檔案開頭的4個位元組的無符號整數稱為魔數(Magic Number)

  • 它的唯一作用是確定這個檔案是否為一個能被虛擬機器接受的有效合法的Class檔案。即:魔數是Class檔案的識別符號。

  • 魔數值固定為0xCAFEBABE。不會改變。

  • 如果一個Class檔案不以0xCAFEBABE開頭,虛擬機器在進行檔案校驗的時候就會直接丟擲以下錯誤:

    Error: A JNI error has occurred, please check your installation and try again
    Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value 1885430635 in class file StringTest
    
  • 使用魔數而不是副檔名來進行識別主要是基於安全方面的考慮,因為副檔名可以隨意地改動。

15.4. 檔案版本號

緊接著魔數的4個位元組儲存的是Class檔案的版本號。同樣也是4個位元組。第5個和第6個位元組所代表的含義就是編譯的副版本號minor_version,而第7個和第8個位元組就是編譯的主版本號major_version。

它們共同構成了class檔案的格式版本號。譬如某個Class檔案的主版本號為M,副版本號為m,那麼這個Class檔案的格式版本號就確定為M.m。

版本號和Java編譯器的對應關係如下表:

15.4.1. Class檔案版本號對應關係

主版本(十進位制) 副版本(十進位制) 編譯器版本
45 3 1.1
46 0 1.2
47 0 1.3
48 0 1.4
49 0 1.5
50 0 1.6
51 0 1.7
52 0 1.8
53 0 1.9
54 0 1.10
55 0 1.11

Java的版本號是從45開始的,JDK1.1之後的每個JDK大版本釋出主版本號向上加1。

不同版本的Java編譯器編譯的Class檔案對應的版本是不一樣的。目前,高版本的Java虛擬機器可以執行由低版本編譯器生成的Class檔案,但是低版本的Java虛擬機器不能執行由高版本編譯器生成的Class檔案。否則JVM會丟擲java.lang.UnsupportedClassVersionError異常。(向下相容)

在實際應用中,由於開發環境和生產環境的不同,可能會導致該問題的發生。因此,需要我們在開發時,特別注意開發編譯的JDK版本和生產環境中的JDK版本是否一致。

  • 虛擬機器JDK版本為1.k(k>=2)時,對應的class檔案格式版本號的範圍為45.0 - 44+k.0(含兩端)。

15.5. 常量池集合

常量池是Class檔案中內容最為豐富的區域之一。常量池對於Class檔案中的欄位和方法解析也有著至關重要的作用。

隨著Java虛擬機器的不斷髮展,常量池的內容也日漸豐富。可以說,常量池是整個Class檔案的基石。

在版本號之後,緊跟著的是常量池的數量,以及若干個常量池表項。

常量池中常量的數量是不固定的,所以在常量池的入口需要放置一項u2型別的無符號數,代表常量池容量計數值(constant_pool_count)。與Java中語言習慣不一樣的是,這個容量計數是從1而不是0開始的。

型別 名稱 數量
u2(無符號數) constant_pool_count 1
cp_info(表) constant_pool constant_pool_count - 1

由上表可見,Class檔案使用了一個前置的容量計數器(constant_pool_count)加若干個連續的資料項(constant_pool)的形式來描述常量池內容。我們把這一系列連續常量池資料稱為常量池集合。

  • 常量池表項中,用於存放編譯時期生成的各種字面量和符號引用,這部分內容將在類載入後進入方法區的執行時常量池中存放

15.5.1. 常量池計數器

constant_pool_count(常量池計數器)

  • 由於常量池的數量不固定,時長時短,所以需要放置兩個位元組來表示常量池容量計數值。

  • 常量池容量計數值(u2型別):從1開始,表示常量池中有多少項常量。即constant_pool_count=1表示常量池中有0個常量項。

  • Demo的值為:

其值為0x0016,也就是22。需要注意的是,這實際上只有21項常量。索引為範圍是1-21。為什麼呢?

通常我們寫程式碼時都是從0開始的,但是這裡的常量池卻是從1開始,因為它把第0項常量空出來了。這是為了滿足後面某些指向常量池的索引值的資料在特定情況下需要表達“不引用任何一個常量池專案”的含義,這種情況可用索引值0來表示。

15.5.2. 常量池表

constant_pool是一種表結構,以1 ~ constant_pool_count - 1為索引。表明了後面有多少個常量項。

常量池主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic References)

它包含了class檔案結構及其子結構中引用的所有字串常量、類或介面名、欄位名和其他常量。常量池中的每一項都具備相同的特徵。第1個位元組作為型別標記,用於確定該項的格式,這個位元組稱為tag byte(標記位元組、標籤位元組)

型別 標誌(或標識) 描述
CONSTANT_Utf8_info 1 UTF-8編碼的字串
CONSTANT_Integer_info 3 整型字面量
CONSTANT_Float_info 4 浮點型字面量
CONSTANT_Long_info 5 長整型字面量
CONSTANT_Double_info 6 雙精度浮點型字面量
CONSTANT_Class_info 7 類或介面的符號引用
CONSTANT_String_info 8 字串型別字面量
CONSTANT_Fieldref_info 9 欄位的符號引用
CONSTANT_Methodref_info 10 類中方法的符號引用
CONSTANT_InterfaceMethodref_info 11 介面中方法的符號引用
CONSTANT_NameAndType_info 12 欄位或方法的符號引用
CONSTANT_MethodHandle_info 15 表示方法控制代碼
CONSTANT_MethodType_info 16 標誌方法型別
CONSTANT_InvokeDynamic_info 18 表示一個動態方法呼叫點

Ⅰ. 字面量和符號引用

在對這些常量解讀前,我們需要搞清楚幾個概念。

常量池主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic References)。如下表:

常量 具體的常量
字面量 文字字串
宣告為final的常量值
符號引用 類和介面的全限定名
欄位的名稱和描述符
方法的名稱和描述符

全限定名

com/atguigu/test/Demo這個就是類的全限定名,僅僅是把包名的“.“替換成”/”,為了使連續的多個全限定名之間不產生混淆,在使用時最後一般會加入一個“;”表示全限定名結束。

簡單名稱

簡單名稱是指沒有型別和引數修飾的方法或者欄位名稱,上面例子中的類的add()方法和num欄位的簡單名稱分別是add和num。

描述符

描述符的作用是用來描述欄位的資料型別、方法的引數列表(包括數量、型別以及順序)和返回值。根據描述符規則,基本資料型別(byte、char、double、float、int、long、short、boolean)以及代表無返回值的void型別都用一個大寫字元來表示,而物件型別則用字元L加物件的全限定名來表示,詳見下表:

標誌符 含義
B 基本資料型別byte
C 基本資料型別char
D 基本資料型別double
F 基本資料型別float
I 基本資料型別int
J 基本資料型別long
S 基本資料型別short
Z 基本資料型別boolean
V 代表void型別
L 物件型別,比如:Ljava/lang/Object;
[ 陣列型別,代表一維陣列。比如:`double[] is [D

用描述符來描述方法時,按照先引數列表,後返回值的順序描述,引數列表按照引數的嚴格順序放在一組小括號“()”之內。如方法java.lang.String tostring()的描述符為()Ljava/lang/String; ,方法int abc(int[]x, int y)的描述符為([II)I。

補充說明:

虛擬機器在載入Class檔案時才會進行動態連結,也就是說,Class檔案中不會儲存各個方法和欄位的最終記憶體佈局資訊。因此,這些欄位和方法的符號引用不經過轉換是無法直接被虛擬機器使用的。當虛擬機器執行時,需要從常量池中獲得對應的符號引用,再在類載入過程中的解析階段將其替換為直接引用,並翻譯到具體的記憶體地址中。

這裡說明下符號引用和直接引用的區別與關聯:

  • 符號引用:符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。符號引用與虛擬機器實現的記憶體佈局無關,引用的目標並不一定已經載入到了記憶體中。

  • 直接引用:直接引用可以是直接指向目標的指標、相對偏移量或是一個能間接定位到目標的控制代碼。直接引用是與虛擬機器實現的記憶體佈局相關的,同一個符號引用在不同虛擬機器例項上翻譯出來的直接引用一般不會相同。如果有了直接引用,那說明引用的目標必定已經存在於記憶體之中了。

Ⅱ. 常量型別和結構

常量池中每一項常量都是一個表,JDK1.7之後共有14種不同的表結構資料。如下表格所示:

根據上圖每個型別的描述我們也可以知道每個型別是用來描述常量池中哪些內容(主要是字面量、符號引用)的。比如:
CONSTANT_Integer_info是用來描述常量池中字面量資訊的,而且只是整型字面量資訊。

標誌為15、16、18的常量項型別是用來支援動態語言呼叫的(jdk1.7時才加入的)。

細節說明:

  • CONSTANT_Class_info結構用於表示類或介面

  • CONSTAT_Fieldref_info、CONSTAHT_Methodref_infoF和lCONSTANIT_InterfaceMethodref_info結構表示欄位、方匯和按口小法

  • CONSTANT_String_info結構用於表示示String型別的常量物件

  • CONSTANT_Integer_info和CONSTANT_Float_info表示4位元組(int和float)的數值常量

  • CONSTANT_Long_info和CONSTAT_Double_info結構表示8字作(long和double)的數值常量

    • 在class檔案的常最池表中,所行的a位元組常借均佔兩個表成員(項)的空問。如果一個CONSTAHT_Long_info和CNSTAHT_Double_info結構在常量池中的索引位n,則常量池中一個可用的索引位n+2,此時常量池長中索引為n+1的項仍然有效但必須視為不可用的。
  • CONSTANT_NameAndType_info結構用於表示欄位或方法,但是和之前的3個結構不同,CONSTANT_NameAndType_info結構沒有指明該欄位或方法所屬的類或介面。

  • CONSTANT_Utf8_info用於表示字元常量的值

  • CONSTANT_MethodHandle_info結構用於表示方法控制代碼

  • CONSTANT_MethodType_info結構表示方法型別

  • CONSTANT_InvokeDynamic_info結構表示invokedynamic指令所用到的引導方法(bootstrap method)、引導方法所用到的動態呼叫名稱(dynamic invocation name)、引數和返回型別,並可以給引導方法傳入一系列稱為靜態引數(static argument)的常量。

解析方法:

  • 一個位元組一個位元組的解析
  • 使用javap命令解析:javap-verbose Demo.class或jclasslib工具會更方便。

總結1:

  • 這14種表(或者常量項結構)的共同點是:表開始的第一位是一個u1型別的標誌位(tag),代表當前這個常量項使用的是哪種表結構,即哪種常量型別。

  • 在常量池列表中,CONSTANT_Utf8_info常量項是一種使用改進過的UTF-8編碼格式來儲存諸如文字字串、類或者介面的全限定名、欄位或者方法的簡單名稱以及描述符等常量字串資訊。

  • 這14種常量項結構還有一個特點是,其中13個常量項佔用的位元組固定,只有CONSTANT_Utf8_info佔用位元組不固定,其大小由length決定。為什麼呢?因為從常量池存放的內容可知,其存放的是字面量和符號引用,最終這些內容都會是一個字串,這些字串的大小是在編寫程式時才確定,比如你定義一個類,類名可以取長取短,所以在沒編譯前,大小不固定,編譯後,通過utf-8編碼,就可以知道其長度。

總結2:

  • 常量池:可以理解為Class檔案之中的資源倉庫,它是Class檔案結構中與其他專案關聯最多的資料型別(後面的很多資料型別都會指向此處),也是佔用Class檔案空間最大的資料專案之一。

  • 常量池中為什麼要包含這些內容?Java程式碼在進行Javac編譯的時候,並不像C和C++那樣有“連線”這一步驟,而是在虛擬機器載入C1ass檔案的時候進行動態連結。也就是說,在Class檔案中不會儲存各個方法、欄位的最終記憶體佈局資訊,因此這些欄位、方法的符號引用不經過執行期轉換的話無法得到真正的記憶體入口地址,也就無法直接被虛擬機器使用。當虛擬機器執行時,需要從常量池獲得對應的符號引用,再在類建立時或執行時解析、翻譯到具體的記憶體地址之中。關於類的建立和動態連結的內容,在虛擬機器類載入過程時再進行詳細講解

15.6. 訪問標誌

訪問標識(access_flag、訪問標誌、訪問標記)

在常量池後,緊跟著訪問標記。該標記使用兩個位元組表示,用於識別一些類或者介面層次的訪問資訊,包括:這個Class是類還是介面;是否定義為public型別;是否定義為abstract型別;如果是類的話,是否被宣告為final等。各種訪問標記如下所示:

標誌名稱 標誌值 含義
ACC_PUBLIC 0x0001 標誌為public型別
ACC_FINAL 0x0010 標誌被宣告為final,只有類可以設定
ACC_SUPER 0x0020 標誌允許使用invokespecial位元組碼指令的新語義,JDK1.0.2之後編譯出來的類的這個標誌預設為真。(使用增強的方法呼叫父類方法)
ACC_INTERFACE 0x0200 標誌這是一個介面
ACC_ABSTRACT 0x0400 是否為abstract型別,對於介面或者抽象類來說,次標誌值為真,其他型別為假
ACC_SYNTHETIC 0x1000 標誌此類並非由使用者程式碼產生(即:由編譯器產生的類,沒有原始碼對應)
ACC_ANNOTATION 0x2000 標誌這是一個註解
ACC_ENUM 0x4000 標誌這是一個列舉

類的訪問許可權通常為ACC_開頭的常量。

每一種型別的表示都是通過設定訪問標記的32位中的特定位來實現的。比如,若是public final的類,則該標記為ACC_PUBLIC | ACC_FINAL。

使用ACC_SUPER可以讓類更準確地定位到父類的方法super.method(),現代編譯器都會設定並且使用這個標記。

補充說明:

  1. 帶有ACC_INTERFACE標誌的class檔案表示的是介面而不是類,反之則表示的是類而不是介面。
    • 如果一個class檔案被設定了ACC_INTERFACE標誌,那麼同時也得設定ACC_ABSTRACT標誌。同時它不能再設定ACC_FINAL、ACC_SUPER 或ACC_ENUM標誌。
    • 如果沒有設定ACC_INTERFACE標誌,那麼這個class檔案可以具有上表中除ACC_ANNOTATION外的其他所有標誌。當然,ACC_FINAL和ACC_ABSTRACT這類互斥的標誌除外。這兩個標誌不得同時設定。
  1. ACC_SUPER標誌用於確定類或接口裡面的invokespecial指令使用的是哪一種執行語義。針對Java虛擬機器指令集的編譯器都應當設定這個標誌。對於Java SE 8及後續版本來說,無論class檔案中這個標誌的實際值是什麼,也不管class檔案的版本號是多少,Java虛擬機器都認為每個class檔案均設定了ACC_SUPER標誌。
    • ACC_SUPER標誌是為了向後相容由舊Java編譯器所編譯的程式碼而設計的。目前的ACC_SUPER標誌在由JDK1.0.2之前的編譯器所生成的access_flags中是沒有確定含義的,如果設定了該標誌,那麼0racle的Java虛擬機器實現會將其忽略。
  1. ACC_SYNTHETIC標誌意味著該類或介面是由編譯器生成的,而不是由原始碼生成的。

  2. 註解型別必須設定ACC_ANNOTATION標誌。如果設定了ACC_ANNOTATION標誌,那麼也必須設定ACC_INTERFACE標誌。

  3. ACC_ENUM標誌表明該類或其父類為列舉型別。

15.7. 類索引、父類索引、介面索引

在訪問標記後,會指定該類的類別、父類類別以及實現的介面,格式如下:

長度 含義
u2 this_class
u2 super_class
u2 interfaces_count
u2 interfaces[interfaces_count]

這三項資料來確定這個類的繼承關係:

  • 類索引用於確定這個類的全限定名

  • 父類索引用於確定這個類的父類的全限定名。由於Java語言不允許多重繼承,所以父類索引只有一個,除了java.1ang.Object之外,所有的Java類都有父類,因此除了java.lang.Object外,所有Java類的父類索引都不為e。

  • 介面索引集合就用來描述這個類實現了哪些介面,這些被實現的介面將按implements語句(如果這個類本身是一個介面,則應當是extends語句)後的介面順序從左到右排列在介面索引集合中。

15.7.1. this_class(類索引)

2位元組無符號整數,指向常量池的索引。它提供了類的全限定名,如com/atguigu/java1/Demo。this_class的值必須是對常量池表中某項的一個有效索引值。常量池在這個索引處的成員必須為CONSTANT_Class_info型別結構體,該結構體表示這個class檔案所定義的類或介面。

15.7.2. super_class(父類索引)

2位元組無符號整數,指向常量池的索引。它提供了當前類的父類的全限定名。如果我們沒有繼承任何類,其預設繼承的是java/lang/object類。同時,由於Java不支援多繼承,所以其父類只有一個。

super_class指向的父類不能是final。

15.7.3. interfaces

指向常量池索引集合,它提供了一個符號引用到所有已實現的介面

由於一個類可以實現多個介面,因此需要以陣列形式儲存多個介面的索引,表示介面的每個索引也是一個指向常量池的CONSTANT_Class(當然這裡就必須是介面,而不是類)。

Ⅰ. interfaces_count(介面計數器)

interfaces_count項的值表示當前類或介面的直接超介面數量。

Ⅱ. interfaces[](介面索引集合)

interfaces[]中每個成員的值必須是對常量池表中某項的有效索引值,它的長度為interfaces_count。每個成員interfaces[i]必須為CONSTANT_Class_info結構,其中0 <= i < interfaces_count。在interfaces[]中,各成員所表示的介面順序和對應的原始碼中給定的介面順序(從左至右)一樣,即interfaces[0]對應的是原始碼中最左邊的介面。

15.8. 欄位表集合

fields

用於描述介面或類中宣告的變數。欄位(field)包括類級變數以及例項級變數,但是不包括方法內部、程式碼塊內部宣告的區域性變數。

欄位叫什麼名字、欄位被定義為什麼資料型別,這些都是無法固定的,只能引用常量池中的常量來描述。

它指向常量池索引集合,它描述了每個欄位的完整資訊。比如欄位的識別符號、訪問修飾符(public、private或protected)、是類變數還是例項變數(static修飾符)、是否是常量(final修飾符)等。

注意事項:

  • 欄位表集合中不會列出從父類或者實現的介面中繼承而來的欄位,但有可能列出原本Java程式碼之中不存在的欄位。譬如在內部類中為了保持對外部類的訪問性,會自動新增指向外部類例項的欄位。

  • 在Java語言中欄位是無法過載的,兩個欄位的資料型別、修飾符不管是否相同,都必須使用不一樣的名稱,但是對於位元組碼來講,如果兩個欄位的描述符不一致,那欄位重名就是合法的。

15.8.1. 欄位計數器

fields_count(欄位計數器)

fields_count的值表示當前class檔案fields表的成員個數。使用兩個位元組來表示。

fields表中每個成員都是一個field_info結構,用於表示該類或介面所宣告的所有類欄位或者例項欄位,不包括方法內部宣告的變數,也不包括從父類或父介面繼承的那些欄位。

標誌名稱 標誌值 含義 數量
u2 access_flags 訪問標誌 1
u2 name_index 欄位名索引 1
u2 descriptor_index 描述符索引 1
u2 attributes_count 屬性計數器 1
attribute_info attributes 屬性集合 attributes_count

15.8.2. 欄位表

Ⅰ. 欄位表訪問標識

我們知道,一個欄位可以被各種關鍵字去修飾,比如:作用域修飾符(public、private、protected)、static修飾符、final修飾符、volatile修飾符等等。因此,其可像類的訪問標誌那樣,使用一些標誌來標記欄位。欄位的訪問標誌有如下這些:

標誌名稱 標誌值 含義
ACC_PUBLIC 0x0001 欄位是否為public
ACC_PRIVATE 0x0002 欄位是否為private
ACC_PROTECTED 0x0004 欄位是否為protected
ACC_STATIC 0x0008 欄位是否為static
ACC_FINAL 0x0010 欄位是否為final
ACC_VOLATILE 0x0040 欄位是否為volatile
ACC_TRANSTENT 0x0080 欄位是否為transient
ACC_SYNCHETIC 0x1000 欄位是否為由編譯器自動產生
ACC_ENUM 0x4000 欄位是否為enum

Ⅱ. 描述符索引

描述符的作用是用來描述欄位的資料型別、方法的引數列表(包括數量、型別以及順序)和返回值。根據描述符規則,基本資料型別(byte,char,double,float,int,long,short,boolean)及代表無返回值的void型別都用一個大寫字元來表示,而物件則用字元L加物件的全限定名來表示,如下所示:

標誌符 含義
B 基本資料型別byte
C 基本資料型別char
D 基本資料型別double
F 基本資料型別float
I 基本資料型別int
J 基本資料型別long
S 基本資料型別short
Z 基本資料型別boolean
V 代表void型別
L 物件型別,比如:Ljava/lang/Object;
[ 陣列型別,代表一維陣列。比如:`double[][][] is [[[D

Ⅲ. 屬性表集合

一個欄位還可能擁有一些屬性,用於儲存更多的額外資訊。比如初始化值、一些註釋資訊等。屬性個數存放在attribute_count中,屬性具體內容存放在attributes陣列中。

// 以常量屬性為例,結構為:
ConstantValue_attribute{
	u2 attribute_name_index;
	u4 attribute_length;
    u2 constantvalue_index;
}

說明:對於常量屬性而言,attribute_length值恆為2。

15.9. 方法表集合

methods:指向常量池索引集合,它完整描述了每個方法的簽名。

  • 在位元組碼檔案中,每一個method_info項都對應著一個類或者介面中的方法資訊。比如方法的訪問修飾符(public、private或protected),方法的返回值型別以及方法的引數資訊等。

  • 如果這個方法不是抽象的或者不是native的,那麼位元組碼中會體現出來。

  • 一方面,methods表只描述當前類或介面中宣告的方法,不包括從父類或父介面繼承的方法。另一方面,methods表有可能會出現由編譯器自動新增的方法,最典型的便是編譯器產生的方法資訊(比如:類(介面)初始化方法()和例項初始化方法())。

使用注意事項:

在Java語言中,要過載(Overload)一個方法,除了要與原方法具有相同的簡單名稱之外,還要求必須擁有一個與原方法不同的特徵簽名,特徵簽名就是一個方法中各個引數在常量池中的欄位符號引用的集合,也就是因為返回值不會包含在特徵簽名之中,因此Java語言裡無法僅僅依靠返回值的不同來對一個已有方法進行過載。但在Class檔案格式中,特徵簽名的範圍更大一些,只要描述符不是完全一致的兩個方法就可以共存。也就是說,如果兩個方法有相同的名稱和特徵簽名,但返回值不同,那麼也是可以合法共存於同一個class檔案中。

也就是說,儘管Java語法規範並不允許在一個類或者介面中宣告多個方法簽名相同的方法,但是和Java語法規範相反,位元組碼檔案中卻恰恰允許存放多個方法簽名相同的方法,唯一的條件就是這些方法之間的返回值不能相同。

15.9.1. 方法計數器

methods_count(方法計數器)

methods_count的值表示當前class檔案methods表的成員個數。使用兩個位元組來表示。

methods表中每個成員都是一個method_info結構。

15.9.2. 方法表

methods[](方法表)

methods表中的每個成員都必須是一個method_info結構,用於表示當前類或介面中某個方法的完整描述。如果某個method_info結構的access_flags項既沒有設定ACC_NATIVE標誌也沒有設定ACC_ABSTRACT標誌,那麼該結構中也應包含實現這個方法所用的Java虛擬機器指令。

method_info結構可以表示類和介面中定義的所有方法,包括例項方法、類方法、例項初始化方法和類或介面初始化方法

方法表的結構實際跟欄位表是一樣的,方法表結構如下:

標誌名稱 標誌值 含義 數量
u2 access_flags 訪問標誌 1
u2 name_index 方法名索引 1
u2 descriptor_index 描述符索引 1
u2 attributes_count 屬性計數器 1
attribute_info attributes 屬性集合 attributes_count

方法表訪問標誌

跟欄位表一樣,方法表也有訪問標誌,而且他們的標誌有部分相同,部分則不同,方法表的具體訪問標誌如下:

標誌名稱 標誌值 含義
ACC_PUBLIC 0x0001 public,方法可以從包外訪問
ACC_PRIVATE 0x0002 private,方法只能本類訪問
ACC_PROTECTED 0x0004 protected,方法在自身和子類可以訪問
ACC_STATIC 0x0008 static,靜態方法

15.10. 屬性表集合

方法表集合之後的屬性表集合,指的是class檔案所攜帶的輔助資訊,比如該class檔案的原始檔的名稱。以及任何帶有RetentionPolicy.CLASS 或者RetentionPolicy.RUNTIME的註解。這類資訊通常被用於Java虛擬機器的驗證和執行,以及Java程式的除錯,一般無須深入瞭解。

此外,欄位表、方法表都可以有自己的屬性表。用於描述某些場景專有的資訊。

屬性表集合的限制沒有那麼嚴格,不再要求各個屬性表具有嚴格的順序,並且只要不與已有的屬性名重複,任何人實現的編譯器都可以向屬性表中寫入自己定義的屬性資訊,但Java虛擬機器執行時會忽略掉它不認識的屬性。

15.10.1. 屬性計數器

attributes_count(屬性計數器)

attributes_count的值表示當前class檔案屬性表的成員個數。屬性表中每一項都是一個attribute_info結構。

15.10.2. 屬性表

attributes[](屬性表)

屬性表的每個項的值必須是attribute_info結構。屬性表的結構比較靈活,各種不同的屬性只要滿足以下結構即可。

屬性的通用格式

型別 名稱 數量 含義
u2 attribute_name_index 1 屬性名索引
u4 attribute_length 1 屬性長度
u1 info attribute_length 屬性表

屬性型別

屬性表實際上可以有很多型別,上面看到的Code屬性只是其中一種,Java8裡面定義了23種屬性。下面這些是虛擬機器中預定義的屬性:

屬性名稱 使用位置 含義
Code 方法表 Java程式碼編譯成的位元組碼指令
ConstantValue 欄位表 final關鍵字定義的常量池
Deprecated 類,方法,欄位表 被宣告為deprecated的方法和欄位
Exceptions 方法表 方法丟擲的異常
EnclosingMethod 類檔案 僅當一個類為區域性類或者匿名類時才能擁有這個屬性,這個屬性用於標識這個類所在的外圍方法
InnerClass 類檔案 內部類列表
LineNumberTable Code屬性 Java原始碼的行號與位元組碼指令的對應關係
LocalVariableTable Code屬性 方法的區域性變數描述
StackMapTable Code屬性 JDK1.6中新增的屬性,供新的型別檢查檢驗器和處理目標方法的區域性變數和運算元有所需要的類是否匹配
Signature 類,方法表,欄位表 用於支援泛型情況下的方法簽名
SourceFile 類檔案 記錄原始檔名稱
SourceDebugExtension 類檔案 用於儲存額外的除錯資訊
Synthetic 類,方法表,欄位表 標誌方法或欄位為編譯器自動生成的
LocalVariableTypeTable 是喲很難過特徵簽名代替描述符,是為了引入泛型語法之後能描述泛型引數化型別而新增
RuntimeVisibleAnnotations 類,方法表,欄位表 為動態註解提供支援
RuntimeInvisibleAnnotations 類,方法表,欄位表 用於指明哪些註解是執行時不可見的
RuntimeVisibleParameterAnnotation 方法表 作用與RuntimeVisibleAnnotations屬性類似,只不過作用物件或方法
RuntimeInvisibleParameterAnnotation 方法表 作用與RuntimeInvisibleAnnotations屬性類似,只不過作用物件或方法
AnnotationDefault 方法表 用於記錄註解類元素的預設值
BootstrapMethods 類檔案 用於儲存invokeddynamic指令引用的引導方法限定符

或者(檢視官網)

部分屬性詳解

① ConstantValue屬性

ConstantValue屬性表示一個常量欄位的值。位於field_info結構的屬性表中。

ConstantValue_attribute{
	u2 attribute_name_index;
	u4 attribute_length;
	u2 constantvalue_index;//欄位值在常量池中的索引,常量池在該索引處的項給出該屬性表示的常量值。(例如,值是1ong型的,在常量池中便是CONSTANT_Long)
}

② Deprecated 屬性

Deprecated 屬性是在JDK1.1為了支援註釋中的關鍵詞@deprecated而引入的。

Deprecated_attribute{
	u2 attribute_name_index;
	u4 attribute_length;
}

③ Code屬性

Code屬性就是存放方法體裡面的程式碼。但是,並非所有方法表都有Code屬性。像介面或者抽象方法,他們沒有具體的方法體,因此也就不會有Code屬性了。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_lenth 儲存位元組碼指令
u2 exception_table_length 1 異常表長度
exception_info exception_table exception_length 異常表
u2 attributes_count 1 屬性集合計數器
attribute_info attributes attributes_count 屬性集合

可以看到:Code屬性表的前兩項跟屬性表是一致的,即Code屬性表遵循屬性表的結構,後面那些則是他自定義的結構。

④ InnerClasses 屬性

為了方便說明特別定義一個表示類或介面的Class格式為C。如果C的常量池中包含某個CONSTANT_Class_info成員,且這個成員所表示的類或介面不屬於任何一個包,那麼C的ClassFile結構的屬性表中就必須含有對應的InnerClasses屬性。InnerClasses屬性是在JDK1.1中為了支援內部類和內部介面而引入的,位於ClassFile結構的屬性表。

⑤ LineNumberTable屬性

LineNumberTable屬性是可選變長屬性,位於Code結構的屬性表。

LineNumberTable屬性是用來描述Java原始碼行號與位元組碼行號之間的對應關係。這個屬性可以用來在除錯的時候定位程式碼執行的行數。

  • start_pc,即位元組碼行號;1ine_number,即Java原始碼行號。

在Code屬性的屬性表中,LineNumberTable屬性可以按照任意順序出現,此外,多個LineNumberTable屬性可以共同表示一個行號在原始檔中表示的內容,即LineNumberTable屬性不需要與原始檔的行一一對應。

// LineNumberTable屬性表結構:
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];
}

⑥ LocalVariableTable屬性

LocalVariableTable是可選變長屬性,位於Code屬性的屬性表中。它被偵錯程式用於確定方法在執行過程中區域性變數的資訊。在Code屬性的屬性表中,LocalVariableTable屬性可以按照任意順序出現。Code屬性中的每個區域性變數最多隻能有一個LocalVariableTable屬性。

  • start pc + length表示這個變數在位元組碼中的生命週期起始和結束的偏移位置(this生命週期從頭e到結尾10)

  • index就是這個變數在區域性變量表中的槽位(槽位可複用)

  • name就是變數名

  • Descriptor表示區域性變數型別描述

// LocalVariableTable屬性表結構:
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];
}

⑦ Signature屬性

Signature屬性是可選的定長屬性,位於ClassFile,field_info或method_info結構的屬性表中。在Java語言中,任何類、介面、初始化方法或成員的泛型簽名如果包含了型別變數(Type Variables)或引數化型別(Parameterized Types),則Signature屬性會為它記錄泛型簽名信息。

⑧ SourceFile屬性

SourceFile屬性結構

型別 名稱 數量 含義
u2 attribute_name_index 1 屬性名索引
u4 attribute_length 1 屬性長度
u2 sourcefile index 1 原始碼檔案素引

可以看到,其長度總是固定的8個位元組。

⑨ 其他屬性

Java虛擬機器中預定義的屬性有20多個,這裡就不一一介紹了,通過上面幾個屬性的介紹,只要領會其精髓,其他屬性的解讀也是易如反掌。

15.11. javap指令

重點關注-v,-c選項。