例子主要是《深入jvm》中的例子,class文件是其中的act.class,java源文件是:
class Act {
public static void doMathForever() {
int i = 0;
for (;;) {
i += 1;
i *= 2;
}
}
}
class文件hex形式:
CA FE BA BE 00 03 00 2D 00 11 07 00 07 07 00 10
0A 00 02 00 04 0C 00 06 00 05 01 00 03 28 29 56
01 00 06 3C 69 6E 69 74 3E 01 00 03 41 63 74 01
00 08 41 63 74 2E 6A 61 76 61 01 00 04 43 6F 64
65 01 00 0D 43 6F 6E 73 74 61 6E 74 56 61 6C 75
65 01 00 0A 45 78 63 65 70 74 69 6F 6E 73 01 00
0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65
01 00 0E 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65
73 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 00
0D 64 6F 4D 61 74 68 46 6F 72 65 76 65 72 01 00
10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63
74 00 20 00 01 00 02 00 00 00 00 00 02 00 09 00
0F 00 05 00 01 00 09 00 00 00 30 00 02 00 01 00
00 00 0C 03 3B 84 00 01 1A 05 68 3B A7 FF F9 00
00 00 01 00 0C 00 00 00 12 00 04 00 00 00 05 00
02 00 07 00 05 00 08 00 09 00 06 00 00 00 06 00
05 00 01 00 09 00 00 00 1D 00 01 00 01 00 00 00
05 2A B7 00 03 B1 00 00 00 01 00 0C 00 00 00 06
00 01 00 00 00 02 00 01 00 0E 00 00 00 02 00 08
- java的class文件是8位二進制流,數據項按順序存放,無間隔,占多字節的數據項以高位在前的順序分幾個字節存放;
- java class基本類型:u1,u2,u4,u8,分別對應:1,2,4,8字節的無符號類型;
- java class file表格展示:(太大了,來個url自己看吧,wikipedia)
- 表項詳解:1)magic(魔數):說白了就是cafebabe,本來java就是咖啡嘛,這4個字節用來區分是否是java的class文件,有則是;2)minor_version&major_version:兩個字節的minor和兩個字節的major,以上為例就是minor:3,major:2D(JDK 1.1);3)再之后就是常量池了,2個字節表示constant_pool_count,本例是17,表示class文件中常量池中的項數(比實際的大1),接著就是常量池,連續的constant_pool_count-1個字節存儲常量池各個入口,常量池入口項解釋見本文第5條筆記;4)在之后的是access_flags:2個字節表示訪問類型,是類還是接口,是public還是private,abstract或者final等等,標志位具體定義見本文第6條筆記,本例是00 20,表示老版本ACC_SUPER;5)之后是this_class,這是2字節的一個對常量池的索引,本例是00 01,即本例的自身類叫做Act;6)之后就是super_class,也是一個2字節常量池索引,指向父類的名字, 本例是00 02,即java.lang.Object;7)interface_count和interfaces:interface_count指出本文件有多少直接實現或者由接口擴展的父接口的數量,本例沒有,故為00 00,之后是interfaces的具體內容,實際的項數就是之前的interface_count數,因為interface_count=0,所以本例的interfaces就沒有值;8)接下來是fields_count和fields:對本類所聲明的字段的描述,首先是個2字節的count,本例是00 00,所以后續也沒有fields的項;9)再之后就是methods_count和methods了,count是一個2字節數據,本例的method_count是00 02,這個count只表示在類或接口中顯式定義的方法,繼承的方法不計數,count后是method_info的表,包含方法的一些信息如方法名、描述符等,本例中00 09表明方法是public(01)&static(08),00 0F是方法名的常量池入口,即常量池的第15項doMathForever,再下來00 05是方法描述符常量池入口,即常量池第5項:()V,然后00 01是屬性表的count數,表示1項屬性,接下來是00 09表示屬性表的常量池入口即常量池第9項Code,接下來的4個字節00 00 00 30表示code屬性長度:48字節,接著00 02是操作數最大數,然后00 01是局部變量存儲長度,這里方法里只有一個變量i,所以是1,然后00 00 00 0c是code字節碼長度12,然后的12個字節就是字節碼code了,再后的00 00是異常數,之后異常棧數是0,跳過,就是00 01的屬性數,然后00 0C指向常量池的第12項即LineNumberTable,這是一個code屬性,之后的00 00 00 12是屬性長度,再后的00 04是line_number_info表的項數,接下來的4項(每項4字節)表示line_number_info,00 00 表示代碼偏移量,00 05表示代碼行號,后面的類似;10)最后是attributes_count和attributes,表明了類的屬性,屬性比較特殊,jvm定義了兩種屬性:SourceCode和InnerClass。
- 常量池各個標志解讀(來源wikipedia)
,這里詳解一下本例中的常量池,常量池是 cp_info
{
tag;
info[];
}
類似這樣的結構,先有一個無符號byte作為tag標志,對應表格中的數據,額外的info字節數組存儲對應的數據index.
我以表格的形式列出常量池的所有數據,應該算一目了然了吧:
-
常量index |
標志 |
標志內容 |
字節 |
具體數據 |
實際含義 |
1 |
07 |
00 07 |
2 |
class reference |
常量池第7項是該class的內容 |
2 |
07 |
00 10 |
2 |
class reference |
常量池第16項是該class的內容 |
3 |
0A |
00 02 00 04 |
4 |
method ref |
兩個index,前兩個字節表示池內的class索引位置,后兩個字節是名字和類型描述 |
4 |
0C |
00 06 00 05 |
4 |
name & type |
就是第3項方法指向的名字和類型的index |
5 |
01 |
00 03 |
2+x |
x個utf-8字符,此處x=3 |
實際值:()V,表示type(第三項方法的類型,具體含義參見描述符定義) |
6 |
01 |
00 06 |
2+x |
x=6 |
實際值:<init>,表示name(第三項方法的名字) |
7 |
01 |
00 03 |
2+x |
x=3 |
實際值:Act,第一項指向的具體字符內容 |
8 |
01 |
00 08 |
2+x |
x=8 |
實際值:Act.java, |
9 |
01 |
00 04 |
2+x |
x=4 |
實際值:Code |
10 |
01 |
00 0D |
2+x |
x=0D=13 |
實際值:ConstantValue |
11 |
01 |
00 0A |
2+x |
x=0A=10 |
實際值:Exceptions |
12 |
01 |
00 0F |
2+x |
x=0F=15 |
實際值:LineNumberTable |
13 |
01 |
00 0E |
2+x |
x=0E=14 |
實際值:LocalVariable |
14 |
01 |
00 0A |
2+x |
x=0A=10 |
實際值:SourceFile |
15 |
01 |
00 0D |
2+x |
x=0D=13 |
實際值:doMathForever |
16 |
01 |
00 10 |
2+x |
x=10=16 |
實際值:java/lang/Object |
- 訪問標志,待完善,這里有詳細的spec
- 描述符的完整定義:非終結符是正常體,終結符是粗體,*號表示之前符號出現0或多次
FieldDescriptor |
FieldType |
ComponentType |
FieldType |
FieldType |
BaseType,ObjectType,ArrayType |
BaseType |
B,C,D,F,I,J,S,Z |
ObjectType |
L<classname>; |
ArrayType |
[ComponentType |
MethodDescriptor |
(ParameterDescriptor*)ReturnDescriptor |
ParameterDescriptor |
FieldType |
ReturnDescriptor |
FieldType,V |
- 基本類型終結符:V代表void
終結符 |
類型 |
B |
byte |
C |
char |
D |
double |
F |
float |
I |
int |
J |
long |
S |
short |
Z |
boolean |
- 一些描述符的例子:
描述符 |
字段或方法聲明 |
I |
int a; |
[[J |
long[][] b; |
[Ljava/lang/Object; |
java.lang.Object[] c; |
Ljava/util/HashMap; |
java.util.HashMap map; |
[[[Z |
boolean[][][] ok; |
()I |
int m1(); |
()Ljava/lang/String; |
String m2(); |
([Ljava/lang/String;)V |
void main(String[] args); |
()V |
void m3(); |
(JI)V |
void m4(long a,int b); |
(Z[Ljava/lang/String;II)Z |
boolean m5(boolean a,String[] b,int c, int d); |
([BII)I |
int m6(byte[] a,int b,int c); |
- 聲明字段時的字段表field_info:
類型 |
名稱 |
數量 |
含義 |
u2 |
access_flags |
1 |
訪問標志 |
u2 |
name_index |
1 |
字段簡單名稱的常量池utf8_info入口索引 |
u2 |
descriptor_index |
1 |
字段描述符的常量池utf8_info入口索引 |
u2 |
attributes_count |
1 |
attribute_info表的項數 |
attribute_info |
attributes |
attributes_count |
字段屬性:ConstantValue, Deprecated, Synthetic(JVM規范) |
- field_info中的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 |
類 |
- 聲明方法時的方法表method_info:
類型 |
名稱 |
數量 |
含義 |
u2 |
access_flags |
1 |
訪問修飾符 |
u2 |
name_index |
1 |
方法簡單名稱的常量池入口 |
u2 |
descriptor_index |
1 |
方法描述符的常量池入口 |
u2 |
attributes_count |
1 |
屬性表的項數 |
attribute_info |
attributes |
attributes_count |
方法屬性:Code,Deprecated, Exceptions和Synthetic(JVM規范) |
- method_info表中的訪問標志對應含義:值得說明的是接口的方法一定是public和abstract的,接口初始化方法可以用strictFP
標志名 |
值 |
含義 |
使用范圍 |
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_NATIVE |
0x0100 |
native |
類 |
ACC_ABSTRACT |
0x0400 |
abstract |
類和接口 |
ACC_STRICT |
0x0800 |
strictFP |
類和接口的<clinit>方法 |
- 類和接口的初始化方法(<clinit>)只有JVM可以直接調用,永遠不會被java字節碼直接調用。
- JVM規范定義的所有屬性:
屬性名 |
使用者 |
描述 |
Code |
method_info |
方法的字節碼和其他數據 |
ConstantValue |
field_info |
final變量的值 |
Deprecated |
field_info,method_info |
字段或方法被禁用的指示符 |
Exceptions |
method_info |
方法可能拋出的可被檢測的異常 |
InnerClasses |
ClassFile |
內部、外部類的列表 |
LineNumberTable |
Code_attribute |
方法的行號與字節碼的映射 |
LocalVariableTable |
Code_attribute |
方法的局部變量的描述 |
SourceFile |
ClassFile |
源文件名 |
Synthetic |
field_info,method_info |
編譯器產生的字段或者方法的指示符 |
- code屬性的表code_attribute:
類型 |
名稱 |
數量 |
含義 |
u2 |
attribute_name_index |
1 |
包含“Code”的常量池入口 |
u4 |
attribute_length |
1 |
去除起始6個字節后的code屬性長度 |
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 |
code屬性:LineNumberTable和LocalVariableTable(JVM規范) |
- 異常表excption_info:
類型 |
名稱 |
數量 |
含義 |
u2 |
start_pc |
1 |
代碼數組起始處到異常處理器起始處的代碼偏移量 |
u2 |
end_pc |
1 |
代碼數組起始處到異常處理器結束后的一個字節的偏移量 |
u2 |
handler_pc |
1 |
代碼數組起始處跳轉到異常處理器的第一條指令的偏移量 |
u2 |
catch_type |
1 |
異常處理器捕獲的異常類型的Class_info常量池入口,如果為0則表示處理finally子句,即處理所有異常 |
- constantValue屬性:各種基礎類型加字符串類型的常量池入口查找,結構很簡單,這里就不列出了。
- LineNumberTable屬性建立了方法字節碼流便宜量和源代碼行號之間的映射關系:
類型 |
名稱 |
數量 |
含義 |
u2 |
attribute_name_index |
1 |
包含“LineNumberTable”的常量池入口 |
u4 |
attribute_length |
1 |
去除起始6字節后的屬性長度 |
u2 |
line_number_table_length |
1 |
line_number_info表長度 |
line_number_info |
line_number_table |
line_number_ table_length |
屬性表 |
- line_number_info表很簡單,就兩個項:u2的start_pc給出新行開始時代碼數組的偏移量,u2的line_number給出了從start_pc開始的行號。
這次就到這里。to be continued...