Class字节码中有两种数据类型
字节数据直接量—基本的数据类型
表(数组)
是由多个基本数据或其他表,按照既定顺序组成的大的数据集合
表是有结构的—体现在:组成表的成分所在的位置和顺序都是已经严格定义好的
使用javap -verbose
命令分析一个字节码文件时,将会分析该字节码文件的魔数,版本号,常量池,类信息,类的构造方法,类中的方法信息,类变量与成员变量信息
长度 | 类型 | 描述 | 数量 | 备注 |
---|---|---|---|---|
u4 | Magic | 魔数值为CAFEBABE 16进制 |
1 | Java创始人James Gosling 制定 |
u2 | minor_version | 次版本号 | 1 | |
u2 | major_version | 主版本号 | 1 | |
u2 | constant_pool_count | 常量池个数 | 1 | |
cp_info | constant_pool | 常量池表 | constant_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 | 域个数 | ||
field_info | fields | 域的表 | fields_count | |
u2 | methods_count | 方法个数 | 1 | |
method_info | methods | 方法表 | methods_count | |
u2 | attributes_count | 附加属性个数 | 1 | |
attribute_info | attributes | 附加属性的表 | attributes_count |
完整的Java字节码结构
ClassFile{
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count - 1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
fields_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
所有.class文件的字节码文件的前4个字节都是魔数,魔数值为固定值—0xCAFEBABE
分为主版本号和次版本号
以java version "1.8.0_131"
为例
主版本号对应的字节码信息
主版本号 | 字节码16进制 |
---|---|
1.8 | 52 |
1.7 | 51 |
1.6 | 50 |
1.5 | 49 |
1.4 | 48 |
1.3 | 47 |
1.2 | 46 |
Java类中定义的方法与变量信息
都是存在在常量池中Java类所对应的常量池主要由常量池数量
与常量池组
这两部分构成
U1
类型—该字节是个标志位,占据一个字节U1
类型来获取元素的数据结构值得注意的是,常量池数组中元素的个数=常量池数-1
(其中0暂时不使用)
不引用任何一个常量池的含义
在JVM规范中,每个变量/字段都有描述信息
根据描述符规则
基本数据类型
和代表无返回值的void类型
都用一个大写字母来表示对象类型
使用字符L+对象的全限定名称
来表示为了压缩字节码文件的体积,对于基本数据,JVM都只使用一个大写字母来表示,如下所示:
Ljava/lang/String
对于数组类型来说,每个维度使用一个前置的[
来表示,如
int[]
被记录为[i
String[][]
被记录为[[Ljava/lang/String
用描述符描述方法时,按照先参数列表,后返回值的顺序来描述
一组()
之内String getUserInfoByIdAndName(int id, String name){}
其描述符为
(I,Ljava/lang/String) Ljava/lang/String
常量 | 项目 | 类型 | 描述 |
---|---|---|---|
CONSTANT_utf8_info | tag | U1 | 值为1 |
length | U2 | UTF-8编码的字符串长度 | |
bytes | U1 | 长度为length的UTF-8编码的字符串 | |
CONSTANT_integer_info | tag | U1 | 值为3 |
bytes | U4 | 按照高位在前存储的int值 | |
CONSTANT_float_info | tag | U1 | 值为4 |
bytes | U4 | 按照高位在前存储的float值 | |
CONSTANT_long_info | tag | U1 | 值为5 |
bytes | U8 | 按照高位在前存储的long值 | |
CONSTANT_Double_info | tag | U1 | 值为6 |
bytes | U8 | 按照高位在前存储的long值 | |
CONSTANT_class_info | tag | 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_info的索引项 | |
CONSTANT_Methodref_info | tag | U1 | 值为10 |
index | U2 | 指向声明方法的类描述符CONSTANT_Class_info的索引项 | |
index | U2 | 指向名称及类型描述符CONSTANT_NameAndType_info的索引项 | |
CONSTANT_Methodref_info | tag | U1 | 值为11 |
index | U2 | 指向声明方法的接口描述符CONSTANT_Class_info的索引项 | |
index | U2 | 指向名称及类型描述符CONSTANT_NameAndType_info的索引项 | |
CONSTANT_NameAndType_info | tag | U1 | 值为12 |
index | U2 | 指向该字段或方法名称常量项的索引 | |
index | U2 | 指向该字段或方法描述符常量项的索引 |
表2中描述了11中数据类型的结构,其实在JDK1.7之后又新增了三种
长度为2个字节,访问标志信息包括
表3. Class access and property modifiers–类访问和属性修饰符
标志名 | 值 | 说明 |
---|---|---|
ACC_PUBLIC | 0x0001 | 声明为public ,可以从其包外部访问 |
ACC_PRIVATE | 0x0002 | 声明为private ,仅共当前类访问 |
ACC_STATIC | 0x0008 | 声明为static |
ACC_FINAL | 0x0010 | 声明为final ,不允许被子类继承 |
ACC_SUPER | 0x0020 | 当调用invokespecial 指令时相应的去调用父类的构造方法 |
ACC_INTERFACE | 0x0200 | 声明为interface 接口,不是类 |
ACC_ABSTRACT | 0x0400 | 声明为abstract ,不能被实例化 |
ACC_SYNTHETIC | 0x1000 | 声明为synthetic ,源代码中不存在 |
ACC_ANNOTATION | 0x2000 | 声明为annotation 注解类型。 |
ACC_ENUM | 0x4000 | 声明为enum 枚举 |
字段表用于描述类和接口中声明的变量
这里的字段包含了
但是不包括方法内部声明的局部变量
表4. 字段表filed_info结构
类型 | 名称 | 数量 |
---|---|---|
u2 | access_flags—访问标志 | 1 |
u2 | name_index—字段名索引 | 1 |
u2 | descriptor_index—描述符索引 | 1 |
u2 | attribute_count—附加属性个数 | 1 |
attribute_info | attributes | attribute_count |
完整的filed_info字节码结构
field_info{
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attribute_count;
attributes[attribute_count];
}
表5. 方法表method_info结构
类型 | 名称 | 数量 |
---|---|---|
u2 | access_flags—访问表示 | 1 |
u2 | name_index—字段名索引 | 1 |
u2 | descriptor_index—描述符索引 | 1 |
u2 | attribute_count—附加属性个数 | 1 |
attribute_info | attributes | attribute_count |
完整的method_info字节码结构
method_info{
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attribute_count;
attributes[attribute_count];
}
attribute
,但是编译器自己也可以实现自己的attribute
写入class文件里,共运行时使用attribute
通过attribute_name_index
来区分表6. 附加属性表attribute_info结构
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index—属性名索引 | 1 |
u4 | attribute_length—属性名长度 | 1 |
u2 | info[attribute_length]—属性信息 | attribute_length |
完整的attribute_info字节码结构
attribute_info{
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
Code attribute
的作用是保存该方法的结构
表7. 代码属性表Code_attribute结构
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index—属性名索引 | 1 |
u4 | attribute_length—attribute所包含的字节数 .不包含attribute_name_index和attribute_length |
1 |
u2 | max_stack—最大栈深度 ,表示这个方法运行时的任何时刻所能到达的操作数栈的最大深度 |
1 |
u2 | max_locals—最大局部变量数 表示这个方法执行期间所创建的局部变量的数目,包含用来表示传入的参数的局部变量 |
1 |
u4 | code_length–该方法所包含的字节码的字节数以及具体的指令码1 | 1 |
u1 | code[code_length] | code_length |
u2 | exception_table_length—异常表长度 | 1 |
exception_table | exception_table[exception_table_length]异常表—存放的是处理异常的信息2 | exception_table_length |
u2 | attributes_count—异常表长度 | 1 |
attributes | attributes[attributes_count]—异常表长度 | attributes_count |
如所对应的字节码
Code_attribute{
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{
u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
}exception_table[exception_table_length];
u2 attributes_count;
attributes[attributes_count];
}
start_pc
和end_pc
表示在code数组中从 start_pc
到 end_pc
(包含前者,不包含后者)的指令抛出的异常会由这个表项来处理handler_pc
表示处理异常的代码的开始处.catch_type
表示会被处理的异常类型,它指向常量池里的一个类,当catch_type
为0时,表示处理所有异常Java字节码对异常的处理方式
JDK 1.4.2
之前的版本中,并不是使用异常表的方式来对异常进行处理的,而是采用特定的指令方式finally
语句块时,现代化的JVM
采取的处理方式是将finally
语句块的字节码拼接到每一个catch
块后面,换句话说,程序中至少存在多个catch块,就会在每一个catch
语句块后面重复多少个finally
语句块的字节码[1] 具体字节码即是该方法被调用时,虚拟机所执行的字节码
[2] 每个exception_table
表项有start_pc
, end_pc
, handler_pc
, catch_pc
组成
LineNumberTable{
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]
}
对于Java类的每一个实例方法
(非static方法),其在编译后所生成的字节码当中,
方法参数的数量总是会比源代码中方法参数的数量多一个(this)
.
它位于方法的第一个参数位置处,这样我们就可以像Java的实例方法中使用this来访问当前对象的属性以及其他方法
这个操作是在编译期间完成的,即由javac编译器在编译时将对this的方法转化为对一个普通实例方法参数的访问
接下来在运行期间,由JVM
在调用实例方法时,自动向实例方法传入该this
参数.
所以.在实例方法的局部变量表中,至少会有一个指向当前对象的局部变量
LocalVariableTable{
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_table_length;
{
u2 start_pc;
u2 length;
u2 name_index;
u2 descripor_index;
u2 index
}local_variable_table[local_variable_table_length]
}
SourceFile{
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}