码库记事本

码库记事本

字节码文件相关知识

小诸哥 0


目录

1、前端编译器vs后端编译器

2、字节码指令

3、class文件的结构

3.1  魔数:Class文件的标志

3.2  Class文件的版本号

3.3 常量池:存放所有常量

3.4 访问标识(access_flag、访问标志、访问标记)

3.5 类索引、父类索引、接口索引集合

3.6 字段表集合

3.6.1 fields_count (字段计数器)

3.6.2 fields[](字段表)

3.7方法表集合

3.8 属性表集合


1、前端编译器vs后端编译器

        javac是一种能够将Java源码编译为字节码的前端编译器。HotSpot VM并没有强制要求前端编译器只能使用javac来编译字节码,其实只要编译结果符合JVM规范都可以被JVM所识别即可。在Java的前端编译器领域,除了javac之外,还有一种被大家经常用到的前端编译器,那就是内置在Eclipse中的ECJ (EclipseCompiler for Java)编译器。和Javac的全量式编译不同,ECJ是一种增量式编译器。

        前端编译器并不会直接涉及编译优化等方面的技术,而是将这些具体优化细节移交给HotSpot的JIT编译器负责。

2、字节码指令

        Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码(opcode)以及跟随其后的零至一个代表此操作所需参数的操作数(operand)所构成。虚拟机中许多指令并不包含操作数,只有一个操作码。

3、class文件的结构

3.1  魔数:Class文件的标志

  • 每个 Class 文件开头的4个字节的无符号整数称为魔数(Magic Number)
  • 它的唯一作用是确定这个文件是否为一个能被虚拟机接受的有效合法的Class文件。即:魔数是Class文件的标识符。
  • 魔数值固定为0xCAFEBABE。不会改变。
  • 如果一个Class文件不以0xCAFEBABE开头,虚拟机在进行文件校验的时候就会直接抛出以下错误:

3.2  Class文件的版本号

  • 紧接着魔数的4个字节存储的是Class文件的版本号。同样也是4个字节。第5个和第6个字节所代表的含义就是编译的副版本号minor_version,而第7个和第8个字节就是编译的主版本号major_version。
  • 不同版本的Java编译器编译的Class文件对应的版本是不一样的。目前,高版本的Java虚拟机可以执行由低版本编译器生成的Class文件,但是低版本的Java虚拟机不能执行由高版本编译器生成的Class文件。否则JVM会抛出java.lang.UnsupportedClassVersionError异常。

3.3 常量池:存放所有常量

  • 在版本号之后,紧跟着的是常量池的数量,以及若干个常量池表项。
  • 常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的无符号数,代表常量池容量计数值(constant_pool_count)。与Java中语言习惯不一样的是,这个容量计数是从1而不是0开始的。

常量池计数器

  • 由于常量池的数量不固定,时长时短,所以需要放置两个字节来表示常量池容量计数值。
  • 常量池容量计数值(u2类型):从1开始,表示常量池中有多少项常量。即constant_pool_count=1表示常量池中有0个常量项.

常量池表: constant_pool [] (常量池)

  • constant_pool是一种表结构,以1 ~ constant_pool_count - 1为索引。表明了后面有多少个常量项。

  • 常量池主要存放两大类常量:字面量(Literal) 和符号引用(Symbolic References)

    • 字面量:基本数据类型,字符串类型常量等
    • 符号引用:类、字段、方法、接口等的符号引用
  • 它包含了class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其他常量。常量池中的每一项都具备相同的特征。第1个字节作为类型标记,用于确定该项的格式,这个字节称为tag byte(标记字节、标签字节)

字面量和符号引用

  • 字面量:文本字符串和声明为final的值
  • 符号引用:类和接口的全限定名,字段的名称和符号引用,方法的名称和符号引用。
    • 描述符:描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。

符号引用和直接引用的区别与关联:

  • 符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到了内存中。
  • 直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那说明引用的目标必定已经存在于内存之中了。

3.4 访问标识(access_flag、访问标志、访问标记)

        在常量池后,紧跟着访问标记。该标记使用两个字节表示,用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为 public类型;是否定义为 abstract类型;如果是类的话,是否被声明为 final等。

3.5 类索引、父类索引、接口索引集合

        在访问标记后,会指定该类的类别、父类类别以及实现的接口,这三项数据来确定这个类的继承关系。

  • 类索引用于确定这个类的全限定名
  • 父类索引用于确定这个类的父类的全限定名。由于 Java语言不允许多重继承,所以父类索引只有一个,除了java.lang.0bject 之外,所有的Java类都有父类,因此除了java.lang.Object外,所有Java类的父类索引都不为0。
  • 接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按 implements 语句(如果这个类本身是一个接口,则应当是 extends语句)后的接口顺序从左到右排列在接口索引集合中。

3.6 字段表集合

        用于描述接口或类中声明的变量。字段(field)包括类变量以及实例变量,但是不包括方法内部、代码块内部声明的局部变量。它指向常量池索引集合,它描述了每个字段的完整信息。比如字段的标识符、访问修饰符(public、private或protected)、是类变量还是实例变量(static修饰符)、是否是常量(final修饰符)等。

注意事项

  • 字段表集合中不会列出从父类或者实现的接口中继承而来的字段,但有可能列出原本Java代码之中不存在的字段。譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。
  • 在Java语言中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的

3.6.1 fields_count (字段计数器)

        fields_count的值表示当前class文件fields表的成员个数。使用两个字节来表示。 fields表中每个成员都是一个field_info结构,用于表示该类或接口所声明的所有类字段或者实例字段,不包括方法内部声明的变量,也不包括从父类或父接口继承的那些字段。

3.6.2 fields[](字段表)

        fields表中的每个成员都必须是一个fields_info结构的数据项,用于表示当前类或接口中某个字段的完整描述。

  • 一个字段的信息包括如下这些信息。这些信息中,各个修饰符都是布尔值,要么有,要么没有。
    • 作用域(public、 private、 protected修饰符)
    • 是实例变量还是类变量(static修饰符)
    • 可变性(final)
    • 并发可见性(volatile修饰符,是否强制从主内存读写
    • 可否序列化 (transient修饰符)
    • 字段数据类型(基本数据类型、对象、数组)I
    • 字段名称

字段访问标识

        我们知道,一个字段可以被各种关键宇去修饰,比如:作用域修饰符(public、private、 protected)、static修饰符、final修饰符、volatile修饰符等等。因此,其可像类的访问标志那样,使用一些标志来标记字段。

 字段名索引

        根据字段名索引的值,查询常量池中的指定索引项即可

描述符索引

        描述符的作用是用来描述字段的数据类型

 属性表集合

        一个字段还可能拥有一些属性,用于存储更多的额外信息。比如初始化值、一些注释信息等。属性个数存放在attribute_count中,属性具体内容存放在attributes数组中。

3.7方法表集合

methods:指向常量池索引集合,它完整描述了每个方法的签名.

  • 在字节码文件中,每一个method_info项都对应着一个类或者接口中的方法信息。比如方法的访问修饰符(public.private或protected),方法的返回值类型以及方法的参数信息等。
  • 如果这个方法不是抽象的或者不是native的,那么字节码中会体现出来。
  • 一方面,methods表只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。另一方面,methods表有可能会出现由编译器自动添加的方法,最典型的便是编译器产生的方法信息(比如:类(接口)初始化方法《clinit》()和实例初始化方法《init》())。

其中包含方法计数器,放发表和方法访问标志。

3.8 属性表集合

  • 方法表集合之后的属性表集合,指的是class文件所携带的辅助信息,比如该class 文件的源文件的名称。以及任何带有RetentionPolicy.CLASS或者RetentionPolicy.RUNTIME的注解。这类信息通常被用于Java虚拟机的验证和运行,以及Java程序的调试,一般无须深入了解。
  • 此外,字段表、方法表都可以有自己的属性表。用于描述某些场景专有的信息。
  • 属性表集合的限制没有那么严格,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,但Java虚拟机运行时会忽略掉它不认识的属性。

属性表集合包含属性计数器,属性表和属性类型

标签: 字节码