Java class文件结构
Class文件是一组以8位字节为基础单位的二进制流,各项数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,如果是超过8位字节以上空间的数据项,则会按照高位在前的方式(Big-Endian)分割成若干个8位字节进行存储。
格式如下
u4 魔数
u2 次版本号
u2 主版本号
u2 常量池计数器 constant_pool_count
常量池 constant_pool_info constant_pool_count-1
u2 访问标志
u2 类索引
u2 父类索引
u2 接口计数器
u2 接口索引集合
u2 字段计数器
字段表集合 field_info
u2 方法计数器
方法表集合 method_info
u2 属性计数器
属性表集合 attribute_info
关于u2,u4的理解
主要有u1、u2、u4、u8,分别代表有1个字节、2个字节、4个字节、8个字节
1、魔数(u4)
每个class文件头4个字节代表魔数,它代表了这个文件是否是一个能被虚拟机接受的Class文件
Java class文件的魔数固定为:cafebabe
2、主次版本号(u4)
前两个字节代表次版本号(minorversion),后两个字节代表主版本号(majorversion)
3、常量池
常量池的大小是不固定的,会根据类中常量的多少来确定
首选由一个2个字节16进制的数来定义常量池长度,计算出常量池的10进制是多少,然后-1得出常量池的数量。
常量池的类型主要包含以下14种类型:
CONSTANT_Utf8_info tag标志位为1, UTF-8编码的字符串
CONSTANT_Integer_info tag标志位为3, 整形字面量
CONSTANT_Float_info tag标志位为4, 浮点型字面量
CONSTANT_Long_info tag标志位为5, 长整形字面量
CONSTANT_Double_info tag标志位为6, 双精度字面量
CONSTANT_Class_info tag标志位为7, 类或接口的符号引用
CONSTANT_String_info tag标志位为8,字符串类型的字面量
CONSTANT_Fieldref_info tag标志位为9, 字段的符号引用
CONSTANT_Methodref_info tag标志位为10,类中方法的符号引用CONSTANT_InterfaceMethodref_info tag标志位为11, 接口中方法的符号引用CONSTANT_NameAndType_info tag 标志位为12,字段和方法的名称以及类型的符号引用
CONSTANT_Method-Handle_info tag标志位为15,方法句柄
CONSTANT_Method-Type_info tag标志位为16,方法类型
CONSTANT_Invoke-Dynamic_info tag标志位为18,动态方法调用点
4、访问标志
由两个字节标示,用于识别类、接口、枚举、注解等相关类型的访问信息,包含这个class是类还是接口,是不是为public类型,是不是abstract,是否声明了final等,标志位的定义如下:
ACC_PUBLIC 0X0001 public类型
ACC_FINAL 0X0010 声明为final,只有类可以设置
ACC_SUPER 0X0020 使用invokespecial字节码指令的新语意,invokespecial指令的语意在JDK1.0.2发生过改变,为了区别这条指令使用哪种语意,JDK1.0.2之后编译出来的类都为真ACC_INTERFACE 0X0200 接口
ACC_ABSTRACT 0X0400 abstract类型,对于接口或者抽象类来说,此标志值为真,其他类为假
ACC_SYNTHETIC 0X1000 这个类并非由用户代码产生
ACC_ANNOTATION 0X2000 注解
ACC_ENUM 0X4000 枚举
5、类索引、父类索引、接口集合
类索引与父类索引都是由两个字节来标识的。
而接下来的接口计数器的两个字节代表该类实现了几个接口,而每个接口都是由2个字节来标识的
6、字段集合
字段集合由字段计数器和字段集合组成,其中字段计数器由两个字节标识,代表有多少个字段,接下来就是字段集合
字段表的结构如下:
类型 名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attribute_count 1
attribute_info attributes attribute_count
字段访问标识包含:
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 0X0100 字段是否由编译器自动产生的
ACC_ENUM 0X0400 字段是否enum
字段描述符包含:
B 基本类型byte
C 基本类型char
D 基本类型double
F 基本类型float
I 基本类型int
J 基本类型long
S 基本类型short
Z 基本类型boolean
V 特殊类型void
L 对象类型,如Ljava/lang/Object
7、方法表集合
用于描述类中的所有方法
方法计数器标识该类中有多少个方法,用2个字节标识,接下来就是方法集合的描述
方法表结构与字段表结构一致
8、属性表集合
class文件、方法表、字段表、属性表都可以有属性表集合,用于描述某些场景专有的信息。
属性计数器标识有多少个属性,用2个字节标识,结下来就是对属性集合的描述。
属性表的结构如下:
类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u1 info attribute_length
属性表类型有以下几种:
属性名称 使用位置 含义
Code 方法表 Java代码编译成的字节码指令
ConstantValue 字段表 final关键字定义的常量值
Deprecated 类,方法表,字段表 被声明为deprecated的方法和字段
Exceptions 方法表 方法抛出的异常
EnclosingMethod 类文件
仅当一个类为局部类或者匿名类时才能拥有这个属性,这个属性用于标识这个类所在外围方法
InnerClasses 类文件 内部类列表
LineNumberTable Code属性 Java源码的行号与字节码指令的对应关系
LocalVariableTable Code属性 方法的局部变量描述
StackMapTable Code属性
JDK1.6中新增的属性,供新的类型检查验证器(Type Checker)检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配
SourceFile 类文件 记录源文件名称
Signature 类,方法表,字段表
JDK1.5中新增的属性,这个属性用于支持泛型情况下的方法签名,在Java语言中,任何类、接 口、初始化方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数化类型(Parameterized Types),则Signature属性会为它记录泛型签名信息。由于Java的泛型采用擦除法实现,在为了避免类型信息被擦除后导致签名混乱,需要这个属性记录泛型中的相关信息SourceDebugExtension 类文件
JDK1.6中新增的属性,SourceDebugExtension 属性用于存储额外的调试信息。譬如在进行JSP文件调试时,无法通过Java堆栈来定位JSP文件的行号,JSR-45规范为这些非Java语言编写,却需要编译成字节码并运行在Java虚拟机中的程序提供了一个进行调试的标准机制,使用 SourceDebugExtension属性就可以用于存储这个标准所新加入的调试信息
Synthetic 类,方法表,字段表 标识方法或字段为编译器自动生成的
LocalVariableTypeTable 类
JDK1.5中新增的属性,它使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
RuntimeVisibleAnnotations 类,方法表,字段表
JDK1.5新增属性,为动态注解提供支持。RuntimeVisibleAnnotations属性用于注明哪些注 解是运行时(实际上运行时就是进行反射调用)可见
RuntimeInvisibleAnnotations 类,方法表,字段表
JDK1.5新增的属性,与RuntimeVisibleAnnotations属性作用刚好相反,用于指明哪些注解是运行时不可见的
RuntimeVisibleParameterAnnotations 方法表
JDK1.5新增的属性,作用与RuntimeVisibleAnnotations属性类似,只不过作用对象为方法参数RuntimeInvisibleParameterAnnotations 方法表
JDK1.5新增的属性,作用与RuntimeInvisibleAnnotations属性类似,只不过作用对象为方法参数
AnnotationDefault 方法表 JDK1.5新增的属性,用于记录注解类元素的默认值BootstrapMethods 类文件 JDK1.7中新增的属性,用于保invokedynamic指令引用的引导方法限定符
以下为分析的一个DEMO
JAVA代码:
package com.data.marketing.util;
public class TestClass {
private int a = 0;
private String string = "ss";
public TestClass() {}
public static void test(){}
public String method(){
return null;
}
public String method2(String str){
return null;
}
public static void main(String[] args) {}
}
字节码:
ca fe ba be 00 00 00 34 00 1e 0a 00 06 00 18 09 00 05 00 19 08 00 1a 09 00 05 00 1b 07 00 1c 07 00 1d 01 00 01 61 01 00 01 49 01 00 06 73 74 72 69 6e 67 01 00 12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 01 00 04 74 65 73 74 01 00 06 6d 65 74 68 6f 64 01 00 14 28 29 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 01 00 07 6d 65 74 68 6f 64 32 01 00 26 28 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 29 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 01 00 04 6d 61 69 6e 01 00 16 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 29 56 01 00 0a 53 6f 75 72 63 65 46 69 6c 65 01 00 0e 54 65 73 74 43 6c 61 73 73 2e 6a 61 76 61 0c 00 0b 00 0c 0c 00 07 00 08 01 00 02 73 73 0c 00 09 00 0a 01 00 21 63 6f 6d 2f 64 61 74 61 2f 6d 61 72 6b 65 74 69 6e 67 2f 75 74 69 6c 2f 54 65 73 74 43 6c 61 73 73 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 00 21 00 05 00 06 00 00 00 02 00 02 00 07 00 08 00 00 00 02 00 09 00 0a 00 00 00 05 00 01 00 0b 00 0c 00 01 00 0d 00 00 00 34 00 02 00 01 00 00 00 10 2a b7 00 01 2a 03 b5 00 02 2a 12 03 b5 00 04 b1 00 00 00 01 00 0e 00 00 00 12 00 04 00 00 00 0c 00 04 00 09 00 09 00 0a 00 0f 00 0d 00 09 00 0f 00 0c 00 01 00 0d 00 00 00 19 00 00 00 00 00 00 00 01 b1 00 00 00 01 00 0e 00 00 00 06 00 01 00 00 00 0f 00 01 00 10 00 11 00 01 00 0d 00 00 00 1a 00 01 00 01 00 00 00 02 01 b0 00 00 00 01 00 0e 00 00 00 06 00 01 00 00 00 12 00 01 00 12 00 13 00 01 00 0d 00 00 00 1a 00 01 00 02 00 00 00 02 01 b0 00 00 00 01 00 0e 00 00 00 06 00 01 00 00 00 16 00 09 00 14 00 15 00 01 00 0d 00 00 00 19 00 00 00 01 00 00 00 01 b1 00 00 00 01 00 0e 00 00 00 06 00 01 00 00 00 1b 00 01 00 16 00 00 00 02 00 17
javap -v的结果
public class com.data.marketing.util.TestClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#24 // java/lang/Object."":()V
#2 = Fieldref #5.#25 // com/data/marketing/util/TestClass.a:I
#3 = String #26 // ss
#4 = Fieldref #5.#27 // com/data/marketing/util/TestClass.string:Ljava/lang/String;
#5 = Class #28 // com/data/marketing/util/TestClass
#6 = Class #29 // java/lang/Object
#7 = Utf8 a
#8 = Utf8 I
#9 = Utf8 string
#10 = Utf8 Ljava/lang/String;
#11 = Utf8
#12 = Utf8 ()V
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 test
#16 = Utf8 method
#17 = Utf8 ()Ljava/lang/String;
#18 = Utf8 method2
#19 = Utf8 (Ljava/lang/String;)Ljava/lang/String;
#20 = Utf8 main
#21 = Utf8 ([Ljava/lang/String;)V
#22 = Utf8 SourceFile
#23 = Utf8 TestClass.java
#24 = NameAndType #11:#12 // "":()V
#25 = NameAndType #7:#8 // a:I
#26 = Utf8 ss
#27 = NameAndType #9:#10 // string:Ljava/lang/String;
#28 = Utf8 com/data/marketing/util/TestClass
#29 = Utf8 java/lang/Object
{
public com.data.marketing.util.TestClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: aload_0
5: iconst_0
6: putfield #2 // Field a:I
9: aload_0
10: ldc #3 // String ss
12: putfield #4 // Field string:Ljava/lang/String;
15: return
LineNumberTable:
line 12: 0
line 9: 4
line 10: 9
line 13: 15
public static void test();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=0, args_size=0
0: return
LineNumberTable:
line 15: 0
public java.lang.String method();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aconst_null
1: areturn
LineNumberTable:
line 18: 0
public java.lang.String method2(java.lang.String);
descriptor: (Ljava/lang/String;)Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=2
0: aconst_null
1: areturn
LineNumberTable:
line 22: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 27: 0
}
字节码解析:
魔数、主次版本号、常量池计数器解析
cafe babe u4 魔数
0000 u2 次版本号 0
0034 u2 主版本号 52
001e u2 常量池计数 30-1 常量池数量转十进制之后-1
常量池解析,共计29个,列举几个不在详细解析
0a u1 tag 10 methodref_info
0006 u2 index #6 java/lang/Object 指向#29
0018 u2 index #24 <init> (#11) 指向#11:#12 ()V (#12)
09 u1 tag 9 Fieldref_info
0005 u2 index #5 com/data/marketing/util/TestClass 指向#28
0019 u2 index #25 a(#7) 指向#7:#8 I(#8)
08 u1 tag 8 String_info
001a u2 index #26 ss
09 u1 tag 9 Fieldref_info
0005 u2 index #5 com/data/marketing/util/TestClass 指向#28
001b u2 index #27 string 指向#9:#10 Ljava/lang/String;
07 u1 tag 7 Class_info
001c u2 index #28 com/data/marketing/util/TestClass
访问标志、类索引、父类索引、接口计数器、接口索引集合、字段计数器
00 21 访问标志(access_flags) u2 0020|0001 public class
00 05 类索引(this_class) u2 #5 TestClass
00 06 父类索引(super_class) u2 #6 Object
00 00 接口计数器(interfaces_class) u2 0代表没有实现接口
接口索引集合(interfaces) u2
00 02 字段计数器(fields_count) u2 2 代表有两个字段
00 02 access_flags u2 0002 字段为private
00 07 字段所在索引 u2 #7 a
00 08 字段描述符索引 u2 #8 I
00 00 属性下字段计数器 u2 0
00 02 access_flags u2 0002 字段为private
00 09 字段所在索引 u2 #9 string
00 0a 字段描述符索引 u2 #10 Ljava/lang/String;
00 00 属性下字段计数器 u2 0
方法计数器、方法表集合(列举一个方法)
Code属性表参考:https://segmentfault.com/a/1190000008722128
00 05 方法计数器(methods_count) u2 5 代表有5个方法
00 01 访问标志(access_flags) u2 0001 代表public
00 0b 方法名称的索引(name_index) u2 #11 <init>
00 0c 方法描述符索引(description_index) u2 #12 ()V 代表返回值是void
00 01 属性计数器(attribute_count) u2 1 代表有一个属性
00 0d 属性名称索引(attribute_name_index) u2 #13 Code 遇见了Code去Code属性表
00 00 00 34 属性长度(attribute_length) u4 52 长度为52个字节
00 02 操作栈深度(max_stack) u2 2 栈深为2
00 01 局部变量表所需的存储空间(max_locals) u2 1 代表局部变量所需存储空间为1 00 00 00 10 字节码指令长度(code_length) u4 16
2aaload_0 将第一个引用类型本地变量
b7 invokespecial 调用超类构造方法,实例初始化方法,私有方法
00 nop 什么都不做
01 aconst_null 将null推送至栈顶
2a aload_0 将第一个引用类型本地变量
03 iconst_0 将int型0推送至栈顶
b5 putfield 为指定的类的实例域赋值
00 nop 什么都不做
02 iconst_m1 将int型-1推送至栈顶
2a aload_0 将第一个引用类型本地变量
12 ldc 将int, float或String型常量值从常量池中推送至栈顶
03 iconst_0 将int型0推送至栈顶
b5 putfield 为指定的类的实例域赋值
00 nop 什么都不做
04 iconst_1 将int型1推送至栈顶
b1 return 从当前方法返回void
00 00 异常表长度(execption_table_length) u2 0 长度为0,所以其异常表没有
00 01 属性表长度(attribute_count) u2 1 代表这个Code里面有一个其他属性表
00 0e 属性名称索引(attribute_name_index) u2 #14 LineNumberTable 遇见了LineNumberTable去LineNumberTable属性表
00 00 00 12 属性长度(attribute_length) u4 18
00 04 行号表长度(line_number_table_length) u2 4
00 00 00 0c 行号表(line_number_info) u4 两个 0:12 前两个为字节码行号,后两个为源码行号
00 04 00 09 行号表(line_number_info) u4 两个 4:9 前两个为字节码行号,后两个为源码行号
00 09 00 0a 行号表(line_number_info) u4 两个 9:10 前两个为字节码行号,后两个为源码行号
00 0f 00 0d 行号表(line_number_info) u4 两个 15:13 前两个为字节码行号,后两个为源码行号