类文件结构
结构图
java源文件,编译为class 之后. class文件的数据结构
将下面这段java源文件为例,分析class文件的数据结构
java 代码
@Deprecated
public class ClassStruct implements Serializable {
private int number = 3;
public int getValue(){
return number;
}
}
字节码
用UltraEdit x64 下, 16进制打开 ClassStruct.class 文件内容如下
基址: 二进制内容: UTF-8 内容:
00000000h: CA FE BA BE 00 00 00 34 00 1B 0A 00 04 00 16 09 ; 漱壕...4........
00000010h: 00 03 00 17 07 00 18 07 00 19 07 00 1A 01 00 06 ; ................
00000020h: 6E 75 6D 62 65 72 01 00 01 49 01 00 06 3C 69 6E ; number...I...<in
00000030h: 69 74 3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 ; it>...()V...Code
00000040h: 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 ; ...LineNumberTab
00000050h: 6C 65 01 00 12 4C 6F 63 61 6C 56 61 72 69 61 62 ; le...LocalVariab
00000060h: 6C 65 54 61 62 6C 65 01 00 04 74 68 69 73 01 00 ; leTable...this..
00000070h: 15 4C 63 6F 6D 2F 61 79 61 2F 43 6C 61 73 73 53 ; .Lcom/aya/ClassS
00000080h: 74 72 75 63 74 3B 01 00 08 67 65 74 56 61 6C 75 ; truct;...getValu
00000090h: 65 01 00 03 28 29 49 01 00 0A 53 6F 75 72 63 65 ; e...()I...Source
000000a0h: 46 69 6C 65 01 00 10 43 6C 61 73 73 53 74 72 75 ; File...ClassStru
000000b0h: 63 74 2E 6A 61 76 61 01 00 0A 44 65 70 72 65 63 ; ct.java...Deprec
000000c0h: 61 74 65 64 01 00 19 52 75 6E 74 69 6D 65 56 69 ; ated...RuntimeVi
000000d0h: 73 69 62 6C 65 41 6E 6E 6F 74 61 74 69 6F 6E 73 ; sibleAnnotations
000000e0h: 01 00 16 4C 6A 61 76 61 2F 6C 61 6E 67 2F 44 65 ; ...Ljava/lang/De
000000f0h: 70 72 65 63 61 74 65 64 3B 0C 00 08 00 09 0C 00 ; precated;.......
00000100h: 06 00 07 01 00 13 63 6F 6D 2F 61 79 61 2F 43 6C ; ......com/aya/Cl
00000110h: 61 73 73 53 74 72 75 63 74 01 00 10 6A 61 76 61 ; assStruct...java
00000120h: 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 01 00 14 6A ; /lang/Object...j
00000130h: 61 76 61 2F 69 6F 2F 53 65 72 69 61 6C 69 7A 61 ; ava/io/Serializa
00000140h: 62 6C 65 00 21 00 03 00 04 00 01 00 05 00 01 00 ; ble.!...........
00000150h: 02 00 06 00 07 00 00 00 02 00 01 00 08 00 09 00 ; ................
00000160h: 01 00 0A 00 00 00 38 00 02 00 01 00 00 00 0A 2A ; ......8........*
00000170h: B7 00 01 2A 06 B5 00 02 B1 00 00 00 02 00 0B 00 ; ?.*.?.?......
00000180h: 00 00 0A 00 02 00 00 00 06 00 04 00 09 00 0C 00 ; ................
00000190h: 00 00 0C 00 01 00 00 00 0A 00 0D 00 0E 00 00 00 ; ................
000001a0h: 01 00 0F 00 10 00 01 00 0A 00 00 00 2F 00 01 00 ; ............/...
000001b0h: 01 00 00 00 05 2A B4 00 02 AC 00 00 00 02 00 0B ; .....*?.?.....
000001c0h: 00 00 00 06 00 01 00 00 00 0C 00 0C 00 00 00 0C ; ................
000001d0h: 00 01 00 00 00 05 00 0D 00 0E 00 00 00 03 00 11 ; ................
000001e0h: 00 00 00 02 00 12 00 13 00 00 00 00 00 14 00 00 ; ................
000001f0h: 00 06 00 01 00 15 00 00 ; ........
约定规则:
- u1 u2 u4 分别表示 无符号整数1字节,无符号整数2字节,无符号整数4字节
- 关于属性表和访问标志,全表内容请参见<深入理解JAVA虚拟机>第6章。 因为版面原因,只附上例子能用的上的表内容
魔数
魔数长度为4个字节: CA FE BA BE
. 唯一的作用就是标志这是一个CLASS文件。
主次版本
- 次版本:
[000000000+4]
2个字节,00 00
- 主版本:
[000000000+4]
2个字节,00 34
主版本 34
的 10进制为 52 表示 JDK8
对应的版本
常量池
JAVA 里面有很多常量池的概念。 这里的常量池是类文件常量池。
通常在JAVA里面说的常量池是 方法区中的运行时常量池
. 他们不是同一个概念 .
常量池是一个很复杂的表结构
常量池结构
- 常量数量
- 常量自定义结构
拿 html 的table 举例:
table 表示常量池
但是常量池下面的结构却不仅仅是tr,tbody,thead.
而是有14个以上的数据结构。 每个数据结构的子结构都不相同
例如:
<常量池>
<字符串表 长度="10">
qwertasdfg
</字符串表>
<方法引用>
<类表 常量池索引="9">
</类表>
<名字类型表 常量池索引="13">
</名字类型表>
</方法引用>
</常量池>
事实上常量池的结构是非常紧凑的,和上面的描述完全不一样
接下来分析当前类的前6个属性表,并附上属性表图
简写常量表
常量 | 项目 | 类型 | 描述 |
CONSTANT_Utf8_info | tag | u1 | 值为1 |
length | u2 | UTF-8字符串编码长度 | |
bytes | length | 长度为length的UTF-8编码字符串 | |
CONSTANT_Class_info | tag | u1 | 值为7 |
index | u2 | 指向全限定名的常量池索引 | |
CONSTANT_Field_info | tag | u1 | 值为9 |
index | u2 | 指向方法的类描述符 CONSTANT_Class_info的索引项 | |
index | u2 | 指向方法的类描述符 CONSTANT_NameAndType 的索引项 | |
CONSTANT_Methodref_into | tag | u1 | 值为10 |
index | u2 | 指向方法的类描述符 CONSTANT_Class_info的索引项 | |
index | u2 | 指向方法的类描述符 CONSTANT_NameAndType 的索引项 |
常量池数量
000000000+0x8 的两个字节 00 1B
表示常量池的总数: 1B 换成10进制就是 27.
表示常量池的数量是 27 - 1 = 26 个常量。
常量池下标从 1 开始。 0是预留的,用来表示不引用常量池的任何常量的意思
方法引用表
000000000+0xA: 0x0A
这里表示类型:CONSTANT_Methodref_info 表示是一个方法引用表
根据简写常量表
的内容, 那么后面跟着的 两个u2类型的就是常量池索引00 04 00 16
分别是 类表索引和 名字类型表索引
接下来的层次分析如图:
字段引用表
000000000+0xE: 0x09
这里表示类型:CONSTANT_Fieldref_info 表示是一个字段引用表
根据简写常量表
的内容, 那么后面跟着的 两个u2类型的就是常量池索引00 03 00 17
和方法引用表的结构一模一样
类表
接下来的三个类表分别指向索引为 18,19,1A 索引的常量
07 00 18
07 00 19
07 00 1A
字符串表
接下来的字符串表: 01 00 06 6E 75 6D 62 65 72
- 01标志:表示是一个字符串
- 长度为6
- 内容: number
后面还有很多相同的结构不一一赘述了. 使用javap -v com/aya/ClassStruct 帮我们反编译出常量池的具体内容如下:
Cconstant pool:
#1 = Methodref #4.#22 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#23 // com/aya/ClassStruct.number:I
#3 = Class #24 // com/aya/ClassStruct
#4 = Class #25 // java/lang/Object
#5 = Class #26 // java/io/Serializable
#6 = Utf8 number
#7 = Utf8 I
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 LocalVariableTable
#13 = Utf8 this
#14 = Utf8 Lcom/aya/ClassStruct;
#15 = Utf8 getValue
#16 = Utf8 ()I
#17 = Utf8 SourceFile
#18 = Utf8 ClassStruct.java
#19 = Utf8 Deprecated
#20 = Utf8 RuntimeVisibleAnnotations
#21 = Utf8 Ljava/lang/Deprecated;
#22 = NameAndType #8:#9 // "<init>":()V
#23 = NameAndType #6:#7 // number:I
#24 = Utf8 com/aya/ClassStruct
#25 = Utf8 java/lang/Object
#26 = Utf8 java/io/Serializable
访问标志
常量池结束后的u2类型的字段00 21
表示类的访问标志。
标志名称 | 标志值 | 含义 |
ACC_PUBLIC | 0x0001 | 是否为public类型 |
ACC_SUPER | 0x0020 | 是否允许使用 invokespecial指令的新语义,在JDK 1.0.2 之后标志必须为真 |
这里就已经明确了类的访问标志 : public
类父类接口索引
类索引 00 03
,通过常量池表得到是: com/aya/ClassStruct
父类索引 00 04
,通过常量池表得到是: java/lang/Object
接口索引 是一个表:
00 01 00 05
表示实现了一个接口,接口的常量池索引是5.
通过常量池表得到是: java/io/Serializable
字段表集合
00 01 00 02 00 06 00 07 00 00
00 01
字段数量00 02
访问标志00 06
名字索引00 07
描述符索引00 00
属性表
字段访问标志表:
标志名称 | 标志值 | 含义 |
ACC_PRIVATE | 0x0001 | 字段是否为private |
描述符标志:
标志名称 | 含义 |
B | 基本类型byte |
C | 基本类型char |
I | 基本类型int |
L | 对象类型,如Ljava/lang/Object |
方法表集合
位置: 00000150h+0x7
方法表: 00 02 00 01 00 08 00 09 00 01 00 0A
含义:
00 02
两个方法00 01
访问标志00 08
名称索引00 09
描述符索引00 01
属性表数量00 0A
属性表名称索引
方法访问标志
标志名称 | 标志值 | 含义 |
ACC_PUBLIC | 0x0001 | 方法是否为public |
根据前面3个的内容
01 08 09
,获得方法信息: public V() (){}
表示类的构造方法。也就是:public ClassStruct(){}
.
无参构造是由javac编译器编译成class文件时,写入字节码中的
这里只有方法的描述,那方法的内容去哪里了呢? 方法的内容在方法的属性表里面
表示无参构造有一个属性表, 属性表的名称索引0x0A
引用的是 Code 字符串。
code属性表结构
类型 | 名称 | 数量 | 含义 |
u2 | attribute_name_index | 1 | 属性表索引. Code 表示Code属性表 |
u4 | attribute_length | 1 | 属性表长度 |
u2 | max_stack | 1 | 操作数栈数量 |
u2 | max_locals | 1 | 局部变量表数量 |
u4 | code_length | 1 | 代码长度 |
u2 | code | code_length | 代码内容 |
u2 | exception_table_length | 1 | 异常表长度 |
u2 | exception_table | exception_table_length | 异常表, 表示 catch 捕捉的字节码区域 |
u2 | attribute_count | 1 | Code属性表的属性表 |
u2 | attribute | attribute_count | Code属性表的属性表长度 |
方法内容
- 属性表索引:
00 0A
字符串:Code - 属性表长度
00 00 00 38
长度为:56
- 操作数栈数量:
00 02
操作数栈为2 - 局部变量表数量:
00 01
局部表量表为1 (this占用的局部变量表第一个)\ - 代码长度:
00 00 00 0A
长度为:10
- 代码内容:
2A B7 00 01 2A 06 B5 00 02 B1
- 异常表长度:
00 00
- 属性表长度:
00 02
代码内容已经确定了, 为什么还有属性表呢? 属性表有什么内容呢?
方法内容属性表-行号表
行号表:
0B 00 00 00 0A 00 02 00 00 00 06 00 04 00 09 00
- LineNumberTable
- 长度为 10
- 内容:
00 02 00 00 00 06 00 04 00 09 00
第一个00 0B
对应常量池的LineNumberTable
, 记录了java源文件和class文件的行号对应。
可以通过参数取消生成行号表, 取消行号表之后就无法调试了
后面行号表的具体内容就不进行分析了
方法内容属性表-本地变量表
本地变量表: 0C 00 00 00 0C 00 01 00 00 00 0A 00 0D 00 0E 00 00 00
- 类型标志:
0C
常量池索引,对应的常量LocalVariableTable
- 长度为
0xC
长度为12 - 内容:
00 01 00 00 00 0A 00 0D 00 0E 00 00 00
方法表2-getValue
01 00 0F 00 10 00 01 00 0A
00 01
访问标志public
00 0F
名称索引getValue
00 10
描述符索引()I
00 01
属性表数量1
00 0A
属性表名称索引Code
和构造方法一样的分析.
方法定义: public int getValue()
;
这里不再赘述方法结构的具体内容
这里通过00 00 00 2F
获得属性表长度为 47. 进行接下来的分析
000001b0h: 01 00 0A 00 00 00 2F 00 01 00 ; ............/...
000001b0h: 01 00 00 00 05 2A B4 00 02 AC 00 00 00 02 00 0B ; .....*?.?.....
000001c0h: 00 00 00 06 00 01 00 00 00 0C 00 0C 00 00 00 0C ; ................
000001d0h: 00 01 00 00 00 05 00 0D 00 0E 00 00
类的属性表
类属性表内容:
00 03 00 11 ; ................
000001e0h: 00 00 00 02 00 12 00 13 00 00 00 00 00 14 00 00 ; ................
000001f0h: 00 06 00 01 00 15 00 00
00 03
, 表示类的属性表有3个属性
类的属性表的相关含义:
标志 | 长度 | 内容 |
常量池索引0x11,对应 SourceFile | 0x02 | 常量池索引0x12,对应: ClassStruct.java |
常量池索引0x13,对应 Deprecated | 0x00 | 表示类被标记为@Deprecated |
常量池索引0x14,对应 RuntimeVisibleAnnotations | 0x06 | 内容: 00 01 00 15 00 00 |
类属性表图:
总结
到此为止,图解class文件结构就结束了。
关于字节码的内容
,行号属性表
等相关内容,都没有深入的说明,这里只是为了让新手更容易理解class文件结构而已。
要深入理解还是要 看书
+Java虚拟机规范
才可以