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

java字节码结构

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

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

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

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

长度 类型 描述 数量 备注
u4 Magic 魔数值为CAFEBABE16进制 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];
}

1 魔数

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

2 版本信息

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

  • 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

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_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之后又新增了三种

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

4 访问标志

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

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

表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枚举

5 字段表filed_info集合

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

  • 类级别变量
  • 实例变量

但是不包括方法内部声明的局部变量
表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];
}

6 方法表method_info集合

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

6.1 附加属性表attribute_info集合

  • 方法中的每个属性都是一个attribute_info结构
  • JVM预定义了部分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];
}

6.2 Code attribute结构

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_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;
}

你可能感兴趣的:(JVM学习笔记8-Java字节码文件结构剖析)