JVM学习笔记9-Java字节码文件结构剖析

java字节码结构

Class字节码中有两种数据类型

  • 字节数据直接量—基本的数据类型

    • u1—代表连续的1个字节组成的整体数据
    • u2—代表连续的2个字节组成的整体数据
    • u4—代表连续的4个字节组成的整体数据
    • u8—代表连续的8个字节组成的整体数据
  • 表(数组)
    是由多个基本数据或其他表,按照既定顺序组成的大的数据集合
    表是有结构的—体现在:组成表的成分所在的位置和顺序都是已经严格定义好的

       使用javap -verbose命令分析一个字节码文件时,将会分析该字节码文件的魔数,版本号,常量池,类信息,类的构造方法,类中的方法信息,类变量与成员变量信息

长度类型描述数量备注
u4Magic魔数值为CAFEBABE16进制1Java创始人James Gosling制定
u2minor_version次版本号1
u2major_version主版本号1
u2constant_pool_count常量池个数1
cp_infoconstant_pool常量池表constant_pool_count-1
u2access_flags类的访问控制权限1
u2this_class类名1
u2super_class父类名1
u2interfaces_count接口个数1
u2interfaces接口名interfaces_count
u2fields_count域个数
field_infofields域的表fields_count
u2methods_count方法个数1
method_infomethods方法表methods_count
u2attributes_count附加属性个数1
attribute_infoattributes附加属性的表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];
}

1 魔数

所有.class文件的字节码文件的前4个字节都是魔数,魔数值为固定值—0xCAFEBABE

2 版本信息

分为主版本号和次版本号
java version "1.8.0_131"为例

  • 1.8表示主版本号
  • 0表示次版本号
  • 131表示更新号

主版本号对应的字节码信息

主版本号字节码16进制
1.852
1.751
1.650
1.549
1.448
1.347
1.246

3 常量池

  • 一个Java类中定义的很多信息都是有常量池类维护和描述的,占据字节码文件内容的很大一部分
  • 可以将常量池看做是Class文件的资源仓库.比如Java类中定义的方法与变量信息都是存在在常量池中
  • 常量池中主要存储主要存储两类常量:
    • 字面量
      • 文本字符串
      • Java中声明为final的常量值等
    • 符号引用
      • 类和接口的全局限定名
      • 字段的名称和描述符
      • 方法的名称和描述符

3.1 常量池的总体结构

Java类所对应的常量池主要由常量池数量常量池组这两部分构成

  • 常量池数量紧跟在主版本数量之后,占据两个字节
  • 常量池组则紧跟在常量池数据之后
    • 常量池数组中不同元素的类型,结构都是不同的,长度自然也不同
    • 每种元素的第一个数据都是U1类型—该字节是个标志位,占据一个字节
    • JVM在解析常量池时,会根据U1类型来获取元素的数据结构

值得注意的是,常量池数组中元素的个数=常量池数-1(其中0暂时不使用)

  • 目的是满足某些常量池索引值的数据在特定情况下需要表达不引用任何一个常量池的含义
  • 根本原因为索引为0也是一个常量,只不过它不位于常量表中,这个常量就对应null值,所以常量池的索引从1而非0开始

在JVM规范中,每个变量/字段都有描述信息

  • 描述信息主要的作用是描述字段的数据类型,方法的参数列表(包括数量,类型与顺序)和返回值

3.2 描述符规则

3.2.1 基本数据类型和对象类型描述符规则

根据描述符规则

  • 基本数据类型和代表无返回值的void类型都用一个大写字母来表示
  • 对象类型使用字符L+对象的全限定名称来表示

为了压缩字节码文件的体积,对于基本数据,JVM都只使用一个大写字母来表示,如下所示:

  • B—byte
  • C—char
  • D—double
  • F— float
  • I—int
  • J—long
  • S—short
  • Z—boolean
  • V—void
  • L—对象类型,如Ljava/lang/String
3.2.2 数组类型的描述符规则

对于数组类型来说,每个维度使用一个前置的[来表示,如

  • int[]被记录为[i
  • String[][]被记录为[[Ljava/lang/String
3.2.3 方法的描述符规则

用描述符描述方法时,按照先参数列表,后返回值的顺序来描述

  • 参数列表按照参数的严格顺序放在一组()之内
    如方法
String getUserInfoByIdAndName(int id, String name){}

其描述符为
(I,Ljava/lang/String) Ljava/lang/String

表1. Class文件结构中常量池中11中数据类型的结构总表
常量项目类型描述
CONSTANT_utf8_infotagU1值为1
lengthU2UTF-8编码的字符串长度
bytesU1长度为length的UTF-8编码的字符串
CONSTANT_integer_infotagU1值为3
bytesU4按照高位在前存储的int值
CONSTANT_float_infotagU1值为4
bytesU4按照高位在前存储的float值
CONSTANT_long_infotagU1值为5
bytesU8按照高位在前存储的long值
CONSTANT_Double_infotagU1值为6
bytesU8按照高位在前存储的long值
CONSTANT_class_infotagU1值为7
indexU2指向全限定名常量项的索引
CONSTANT_String_infotagU1值为8
indexU2指向字符串字面量的索引
CONSTANT_Fieldref_infotagU1值为9
indexU2指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项
indexU2指向字段描述符CONSTANT_NameAndType_info的索引项
CONSTANT_Methodref_infotagU1值为10
indexU2指向声明方法的类描述符CONSTANT_Class_info的索引项
indexU2指向名称及类型描述符CONSTANT_NameAndType_info的索引项
CONSTANT_Methodref_infotagU1值为11
indexU2指向声明方法的接口描述符CONSTANT_Class_info的索引项
indexU2指向名称及类型描述符CONSTANT_NameAndType_info的索引项
CONSTANT_NameAndType_infotagU1值为12
indexU2指向该字段或方法名称常量项的索引
indexU2指向该字段或方法描述符常量项的索引

表2中描述了11中数据类型的结构,其实在JDK1.7之后又新增了三种

  • CONSTANT_MethodHandle_info
  • CONSTANT_MethodTypeInfo
  • CONSTANT_InvokeDynamic_info
    所以一共是14种

4 访问标志

长度为2个字节,访问标志信息包括

  • 该Class文件是类还是接口
  • 是否被定义为public
  • 是否是abstract
  • 如是类是否被声明成final

表3. Class access and property modifiers–类访问和属性修饰符

标志名说明
ACC_PUBLIC0x0001声明为public,可以从其包外部访问
ACC_PRIVATE0x0002声明为private,仅共当前类访问
ACC_STATIC0x0008声明为static
ACC_FINAL0x0010声明为final,不允许被子类继承
ACC_SUPER0x0020当调用invokespecial指令时相应的去调用父类的构造方法
ACC_INTERFACE0x0200声明为interface接口,不是类
ACC_ABSTRACT0x0400声明为abstract,不能被实例化
ACC_SYNTHETIC0x1000声明为synthetic,源代码中不存在
ACC_ANNOTATION0x2000声明为annotation注解类型。
ACC_ENUM0x4000声明为enum枚举

5 字段表filed_info集合

字段表用于描述类和接口中声明的变量
这里的字段包含了

  • 类级别变量
  • 实例变量

但是不包括方法内部声明的局部变量
表4. 字段表filed_info结构

类型名称数量
u2access_flags—访问标志1
u2name_index—字段名索引1
u2descriptor_index—描述符索引1
u2attribute_count—附加属性个数1
attribute_infoattributesattribute_count

完整的filed_info字节码结构

field_info{
	u2	access_flags;
	u2	name_index;
	u2	descriptor_index;
	u2	attribute_count;
	attributes[attribute_count];
}

6 方法表method_info集合

表5. 方法表method_info结构

类型名称数量
u2access_flags—访问表示1
u2name_index—字段名索引1
u2descriptor_index—描述符索引1
u2attribute_count—附加属性个数1
attribute_infoattributesattribute_count

完整的method_info字节码结构

method_info{
	u2	access_flags;
	u2	name_index;
	u2	descriptor_index;
	u2	attribute_count;
	attributes[attribute_count];
}

6.1 附加属性表attribute_info集合

  • 方法中的每个属性都是一个attribute_info结构
  • JVM预定义了部分attribute,但是编译器自己也可以实现自己的attribute写入class文件里,共运行时使用
  • 不同的attribute通过attribute_name_index来区分

表6. 附加属性表attribute_info结构

类型名称数量
u2attribute_name_index—属性名索引1
u4attribute_length—属性名长度1
u2info[attribute_length]—属性信息attribute_length

完整的attribute_info字节码结构

attribute_info{
	u2	attribute_name_index;
	u4	attribute_length;
	u1	info[attribute_length];
}

6.2 Code attribute结构

Code attribute的作用是保存该方法的结构

表7. 代码属性表Code_attribute结构

类型名称数量
u2attribute_name_index—属性名索引1
u4attribute_length—attribute所包含的字节数.不包含attribute_name_index和attribute_length1
u2max_stack—最大栈深度,表示这个方法运行时的任何时刻所能到达的操作数栈的最大深度1
u2max_locals—最大局部变量数表示这个方法执行期间所创建的局部变量的数目,包含用来表示传入的参数的局部变量1
u4code_length–该方法所包含的字节码的字节数以及具体的指令码11
u1code[code_length]code_length
u2exception_table_length—异常表长度1
exception_tableexception_table[exception_table_length]异常表—存放的是处理异常的信息2exception_table_length
u2attributes_count—异常表长度1
attributesattributes[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_pcend_pc表示在code数组中从 start_pcend_pc(包含前者,不包含后者)的指令抛出的异常会由这个表项来处理
  • handler_pc表示处理异常的代码的开始处.
  • catch_type表示会被处理的异常类型,它指向常量池里的一个类,catch_type为0时,表示处理所有异常

Java字节码对异常的处理方式

  1. 统一采用异常表的方式来对异常进行处理
  2. JDK 1.4.2之前的版本中,并不是使用异常表的方式来对异常进行处理的,而是采用特定的指令方式
  3. 当异常存在finally语句块时,现代化的JVM采取的处理方式是将finally语句块的字节码拼接到每一个catch块后面,换句话说,程序中至少存在多个catch块,就会在每一个catch语句块后面重复多少个finally语句块的字节码

[1] 具体字节码即是该方法被调用时,虚拟机所执行的字节码
[2] 每个exception_table表项有start_pc, end_pc, handler_pc, catch_pc组成

6.3 LineNumberTable的结构

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]
}

6.4 LocalVariableTable的结构

对于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]
}

7 SourceFile的结构

SourceFile{
	u2	attribute_name_index;
	u4	attribute_length;
	u2	sourcefile_index;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值