Class文件結構


Java之所以能實現“Write Once, Run Anywhere”,是因為不同平台的虛擬機都統一使用一種程序存儲格式——字節碼。Java虛擬機不和包括Java在內的任何語言綁定,它只於“Class”文件這種特定的二進制文件格式所關聯。

Class文件是一組以8位字節為基礎單位的二進制流,各個數據項目嚴格按照順序緊湊排列在Class文件中,中間無任何分隔符。

 

明確兩個概念:無符號數和表

無符號數屬於基本的數據類型,以u1、u2、u4來分別代表1個字節、2個字節和4個字節的無符號數。

表是由多個無符號數或者其他表作為數據項構成的復合數據結構,整個Class文件本質上就是一張表。

Class文件格式
類型 名稱 數量 描述
u4 magic 1 魔數
u2 minor_version 1 次版本號
u2 major_version  1 主版本號
u2 constant_pool_count 1 常量池容量
cp_info constant_pool costant_pool_count-1 常量池
u2 access_flags 1 訪問標志
u2 this_class 1 當前類常量索引
u2 super_class 1 超類常量索引
u2 interfaces_count 1 接口數量
u2 interfaces interfaces_count 接口常量索引
u2 fields_count 1 字段數量
field_info fields fields_count 字段信息
u2 methods_count 1 方法數量
method_info methods methods_count 方法信息
u2 attributes_count 1 屬性數量
attribute_info attributes attributes_count 屬性信息

 

解釋Class文件格式之前,先編寫一個簡單的java類

1 package com.yyl.Test;
2 public class Test{
3     private int i = 2;
4     
5     public int getResult(){
6         return i + 2;
7     }
8 }

編譯成class文件后,用winhex軟件打開class字節碼

 

使用javap命令幫助分析

 

1、魔數(magic)

每個Class文件頭4個字節稱為魔數,它的唯一作用是確定這個文件能否為一個能為虛擬機接收的Class文件,基於安全考慮,使用魔數而不是擴展名來進行身份識別。從16進制字節碼中看出前4個字節為CAFEBABE(咖啡寶貝?)。

 

2、次/主版本號(minor_version/major_version)

緊接着魔數的4個字節分別是次版本號和主版本號,java的版本號是從45開始,高版本的JDK能向下兼容以前版本的Class文件,虛擬機拒絕執行超過其版本號的Class文件。從16進制字節碼中看出次版本號0x0000,主版本號0x0032。

 

3、常量池容量、常量池(constant_pool_count、constant_pool)

常量池主要存放兩大類常量:

a.字面值:接近java語言層面的常量概念,如文本字符串、final常量值等。

b.符號引用:類和接口的全限定名、字段的名稱和描述符、方法的名稱和描述符。

由於常量池中常量的數量是不固定的,所以在常量池入口需要設置一項u2類型數據,代表常量池容量計數值。其計數不是從0開始,而是從1開始。

如圖,常量池容量值為0x0013,即十進制19,因此容量為19-1=18個。查看javap命令輸出的常量表中也可以看出Constant pool總共有18個常量。

設計者把第0項空出來目的在於在特定情況下需要表達“不引用任何一個常量池項目”的含義。

常量池項目結構第一項均為u1類型的tag,該標志代表常量池項目的類型,而其他結構各異。

下面只列出部分結構:

常量池中常量項結構
常量 項目 類型 描述
CONSTANT_Utf8_info tag u1 值為1
length u2 占用字節數
bytes u1 長度為length的UTF-8編碼的字符串
CONSTANT_Integer_info tag u1 值為3
bytes u4 按照高位在前存儲的int值
CONSTANT_Class_info teg u1 值為7
index u2 指向全限定名常量項的索引
CONSTANT_String_info  tag u1 值為8
index u2 指向字符串字面值的索引
CONSTANT_Fieldref_info tag u1 值為9
index u2 指向聲明字段的類或接口描述符CONSTANT_Class_info的索引項
index u2 指向字段描述符CONSTANT_NameAndType的索引項
CONSTANT_Methodref_info tag u1 值為10
index u2 指向聲明方法的類描述符CONSTANT_Class_info的索引項
index u2 指向方法描述符CONSTANT_NameAndType_info的索引項
CONSTANT_NameAndType_info tag u1 值為12
index u2 指向該字段或方法名稱常量項的索引
index u2 指向該字段或方法描述符常量項的索引

接着分析字節碼:

如圖可以看出第一個常量項tag為0x0A,即十進制10,類型為CONSTANT_Methodref_info,接着的u2類型0x0004指向類的索引,即指向java/lang/Object類,后面的0x000F指向方法描述符,即指向方法名為<init>返回值為()V的描述符。所以整個常量項就表示如圖中的“結果”。

其他常量項類似上面方法,就不一一闡述。

另外,由於Class文件中方法、字段等都需要引用CONSTAN_Utf8_info型常量來描述名稱,所以該類型最大長度也就是java中方法、字段名的最大長度(u2類型表達的最大值為65535),所以如果定義了超過64KB英文字符的變量或方法名,將無法編譯。

 

4、訪問標志(access_flags)

常量池結束后,緊接着的兩個字節表示訪問標志,用於識別類或接口層次的訪問信息,包括這個Class是類還是接口,是否定義為public類型、abstract類型等等。

訪問標志
標志名稱 標志值 含義
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 標識這是一個枚舉

Test類被public關鍵字修飾,因此它的ACC_PUBLIC、ACC_SUPER標志應當為真,因此其access_flags值應為0x0001|0x0020=0x0021。

 

5、類索引、父類索引與接口索引集合(this_class、super_class、interfaces)

類索引和父類索引都是一個u2類型的數據,接口索引集合是一組u2類型的數據的集合。它們各自指向一個類型為CONSTANT_Class_info的類描述符常量。

 

 

6、字段表集合(field_info)

字段表用於描述接口或類中聲明的變量,字段包括類級變量以及實例級變量,但不包括在方法內部聲明的局部變量。

字段表結構
類型 名稱 數量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

字段修飾符放在access_flags項目中,如下表

字段訪問標志
標志名稱 標志值 含義
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_TRANSIENT 0x0080 字段是否transient
ACC_SYNTHETIC 0x1000 字段是否由編譯器自動產生
ACC_ENUM 0x4000 字段是否為enum

跟隨access_flags的標志是兩項索引值:name_index和descriptor_index,他們都是對常量池的引用,分別代表字段的簡單名稱以及字段和方法的描述符。

區分三個概念:全限定名、簡單名稱、描述符

a.全限定名,如java/lang/Object,僅僅將類全名中的“.”替換成“/”。

b.簡單名稱是指沒有類型和參數修飾的方法或者字段名稱,如類中getResult()方法和i字段的簡單名稱分別為“getResult”和“i”。

c.描述符是用來描述字段的數據類型、方法的參數列表(包括數量、類型以及順序)和返回值。

描述符標識字符含義
標識字符 含義 標識字符 含義
B 基本類型byte J 基本類型long
C 基本類型char S 基本類型short
D 基本類型double Z 基本類型boolean
F 基本類型float V 特殊類型void
I 基本類型int             L 對象類型,如Ljava/lang/Object

對於數組類型,每一唯獨使用一個前置的“[”字符描述,如一個定義為“java.lang.String[][]”類型的二維數組,將被記錄為“[[Ljava/lang/String”。

當描述符描述方法時,按照先參數列表后返回值的順序描述,參數列表按照參數的嚴格順序放在一組小括號“()”之內。如int getResult()方法的描述符為“()I”。

字段表最后的屬性表結構可用於存儲一些額外的信息。

 

7、方法表集合(method_info)

方法表的結構跟字段表結構一樣,依次包括access_flags、name_index、descriptor_index、attributes,而訪問標志則有所區別。

方法訪問標志
標志名稱 標志值 含義
ACC_PUBLIC 0x0001 方法是否為public類型
ACC_PRIVATE 0x0002 方法是否為private
ACC_PROTECTED 0x0004 方法是否為protected
ACC_STATIC 0x0008 方法是否為static
ACC_FINAL 0x0010 方法是否為final
ACC_SYNCHRONIZED 0x0020 方法是否為synchronized
ACC_BRIDGE 0x0040 方法是否由編譯器產生的橋接方法
ACC_VARARGS 0x0080 方法是否接收不定參數
ACC_NATIVE 0x0100 方法是否為native
ACC_ABSTRACT 0x0400 方法是否為abstract
ACC_STRICTFP 0x0800 方法是否為strictfp
ACC_SYNTHETIC 0x1000 字段是否由編譯器自動產生

方法定義可以由訪問標志、名稱索引、描述符表達清楚,而方法里面的代碼經過編譯器編譯成字節碼指令后,存放在方法屬性表集合中一個名為“Code”的屬性里面。

 

8、屬性表集合(attribute_info)

在Class文件、字段表、方法表都可以攜帶自己的屬性表集合,以用於描述某些場景專有的信息。

下文只介紹了其中一些屬性:

Code屬性表結構
類型 名稱 數量 描述
u2 attribute_name_index 1 常量值固定為Code,代表該屬性名稱
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 attributes_count 1 屬性集合中屬性個數
attribute_info attributes attributes_count 屬性信息

其中max_locals代表局部變量的存儲空間,單位為Slot(虛擬機為局部變量分配內存所使用的最小單位)。對於byte、char、float、int、short等長度不超過32位的數據類型,每個局部變量占用1個Slot,而double和long這兩種64位的數據類型則需要兩個Slot來存放。方法參數,包括實例方法中的隱藏參數“this”、顯式異常處理器的參數、方法體中定義的局部變量都需要使用局部變量表來存放。max_locals並不是簡單將所有局部變量所占Slot之和作為其值,java編譯器會根據變量的作用域來分配Slot給各個變量使用,然后計算max_locals的大小。

字節碼中每個u1類型的單字節代表一個指令。意義請自行查找虛擬機字節碼指令表。

 

異常表結構
類型 名稱 數量 類型 名稱 數量
u2 start_pc 1 u2 handler_pc 1
u2 end_pc 1 u2 catch_type 1

這些字段的含義為:如果當字節碼在第start_pc行(相對於方法體開始的偏移量)到第end_pc行(不包括)之間出現類型為catch_type或其子類的異常,則轉到handler_pc行繼續處理。當catch_type為0時,代表任意異常情況都需要轉向handler_pc行處處理。

 

LineNumberTable屬性結構
類型 名稱 數量
u2 attribute_name_index 1
u4 attribute_length 1
u2 line_number_table_length 1
line_number_info line_number_table line_number_table_length

該屬性用於描述java源碼行號與字節碼行號(偏移量)之間的對應關系,line_number_table是一個數量為line_number_table_length、類型為line_number_info的集合,line_number_info表包括start_pc和line_number兩個u2類型的數據項,前者是字節碼行號,后者是java源碼行號。

后面是另一個方法的字節碼,就不再贅述。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM