字节码初探

字节码初探

java文件在经过编译器编译后会生成.class文件,那么这个类型的文件内容或者格式是什么样子的呢?

注意,任何一个class文件都存着对应的唯一一个类或接口定义的信息,但是类和接口并不一定定义在class文件中,例如可以动态生成直接进入类加载器。

每个类都会生成一个class文件,即使一个java文件有多个类也会为每一个类都生成一个class文件

class文件中的数据类型

u2代表两个字节的无符号数,以此类推u1, u4, u8

class文件中的数据没有分隔符,采用大端存储,所以对每一个数据都要严格排序

 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;
 field_info fields[fields_count];
 u2 methods_count;
 method_info methods[methods_count];
 u2 attributes_count;
 attribute_info attributes[attributes_count];
  1. 首先是一个class文件的标准开头0XCAFEBABE(魔数),不是该字符串起始的class文件jvm都不编译;

  2. 次版本号和主版本号,可以向下兼容编译,即高版本可以编译低版本的class文件;

  3. 常量池:(JDK13前)

    每一个常量都是一个表来描述,目前一共有17个不同的常量类型,主要包括:

    • 被模块导出的或者开放的包(package)

    • 类和接口的全限定名

    • 字段的名称和描述符

    • 方法的名称和描述符

    • 方法的句柄方法类型

    • 动态调用点和动态常量(不懂,待补充)

    常量池中的所有表项:

    在这里插入图片描述

    img

    img

    img

    eg:CONSTANT_Methodref_info这个表中就包括

    1. tag-对应该表的类别,用以以后根据类别确定表里的信息

    2. index-指向对应的CONSTANT_class_info表,即该方法属于哪一个类(CONSTANT_class_info储存一个tag与一个指向类的全限定名的index)

    3. index-指向对应的CONSTANT_NameAndType_info表类型(内有tag,指向字段或方法的名称索引,指向字段或方法描述符的索引。

    其他表或嵌套表或自成一派,皆以此类推。

    此处要注意,java没有在编译的时候进行链接这一步,只会在虚拟机加载class文件的时候进行动态链接,也就是说,class文件并不会保存各个方法、字段在内存中的布局信息,不经过编译器运行时的转换无法获得真正的内存入口地址。(此处涉及到类加载机制,待补充到下一篇文章)。

    1. 访问标志

    用于识别一些类或接口的访问信息,比如public、final等,还有是类类型还是接口类型。

    一共有16个标志位可以使用,但是目前只定义了九个。

    1. 类索引,父类索引和接口索引的集合

    类索引父类索引分别用一个u2类型的数据结构表示,各自指向一个常量池中的CONSTANT_Class_info类型中的全限定名称,接口索引集合首先记录集合接口的数量,再指向每一个CONSTANT_Class_info(猜测)

    1. 字段表集合(field_info)

    该字段包括类级变量以及实例中的变量,不包括方法中的局部变量。-

    一. access_flags描述:描述类中或者接口中的变量。是一个u2的数据类型,每一个位代表一个bool值,比如:

    • 字段的作用域;

    • 类变量还是实例变量;

    • 可变性(final)

    • 并发可见性(volatile)

    • 能否被序列化(transient)

    • 字段数据类型(基本数据类型,对象,数组)

    • 字段名称

    二. name_index and descriptor_index:

    都是对常量池的引用,分别对应简单名称描述符(都是u2)

    字段和方法的描述符:用来描述数据类型方法的参数列表,返回值,基本数据类型和void用一个大写字符表示,对象类型用大写L加上对象的全限定名表示。对于数组类型,用每一个维度用前置的[字符来描述。

    描述符描述方法的时候,按照先参数列表,再返回值的顺序描述。不会列出从父类或父接口中继承来的字段,但是编译器有可能新加指向外面的字段。

    三. (u2)attributes_count 和(attribute_info)attributes

    descriptor_index后面可以跟的属性表集合,可以描述一个或者多项附加信息。

    字段表中不会出现父类或者父接口中继承而来的字段信息,但是可能代码中不存在的字段信息(例如内部类为了访问外部增加外部的实例)

    字段不可以重载,类中的字段名称必须都不一样

    1. 方法表集合

    与字段表集合类似,但是没有volatile和transient字段,也可能会增加一些额外的方法。

    另外,在java文件的特征签名(一个方法中各个参数在常量池中引用的集合)来验证是否是重载,由于返回值不包含在特征签名中,所以无法依靠返回值不同对一个方法进行重载。但是class文件中的特征前面是包括返回值的,也就是说只要返回值不同就可以同存在与一个class文件中。

    方法里面的代码放入到Code属性中

    1. 属性表集合

    用以描述class文件字段表方法表中的额外信息属性表的总体结构:

    对于每一个属性,他的名称都要从常量池中引入一个CONSTANT_Utf8_info类型的常量表示,而属性的结构这是自定义的,只需要一个length说明属性的长度。

    名称类型属性
    u2 attribute_name_index 1
    u4 attribute_length 1
    u1 info attribute_length

      attribute_info又可细分为以下21种(即《Java虚拟机规范(Java SE 7)》中预定义了的21项虚拟机实现应当能识别的属性):

    属性名称使用位置含义
    Code 方法表中 Java代码编译成的字节码指令(即:具体的方法逻辑字节码指令)
    ConstantValue 字段表中 final关键字定义的常量值
    Deprecated 类 中、方法表中、字段表中 被声明为deprecated的方法和字段
    Exceptions 方法表中 方法声明的异常
    LocalVariableTable Code属性中 方法的局部变量描述
    LocalVariableTypeTable 类中 JDK1.5中新增的属性,它使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
    InnerClasses 类中 内部类列表
    EnclosingMethod 类中 仅当一个类为局部类或者匿名类时,才能拥有这个属性,这个属性用于表示这个类所在的外围方法
    LineNumberTable Code属性中 Java源码的行号与字节码指令的对应关系
    StackMapTable Code属性中 JDK1.6中新增的属性,供新的类型检查验证器(Type Checker)检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配
    Signature 类中、方法表中、字段表中 JDK1.5新增的属性,这个属性用于支持泛型情况下的方法签名,在Java语言中,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数类型(Parameterized Types),则Signature属性会为它记录泛型签名信息。由于Java的泛型采用擦除法实现,在为了避免类型信息被擦除后导致签名混乱,需要这个属性记录泛型中的相关信息
    SourceFile 类中 记录源文件名称
    SourceDebugExtension 类中 JDK1.6中新增的属性,SourceDebugExtension用于存储额外的调试信息。如在进行JSP文件调试时,无法通过Java堆栈来定位到JSP文件的行号,JSR-45规范为这些非Java语言编写,却需要编译成字节码运行在Java虚拟机汇中的程序提供了一个进行调试的标准机制,使用SourceDebugExtension就可以存储这些调试信息。
    Synthetic 类中、方法表中、字段表中 标识方法或字段为编译器自动产生的
    RuntimeVisibleAnnotations 类中、方法表中、字段表中 JDK1.5中新增的属性,为动态注解提供支持。RuntimeVisibleAnnotations属性,用于指明哪些注解是运行时(实际上运行时就是进行反射调用)可见的。
    RuntimeInvisibleAnnotations 类中、方法表中、字段表中 JDK1.5中新增的属性,作用与RuntimeVisibleAnnotations相反用于指明哪些注解是运行时不可见的。
    RuntimeVisibleParameterAnnotations 方法表中 JDK1.5中新增的属性,作用与RuntimeVisibleAnnotations类似,只不过作用对象为方法的参数。
    RuntimeInvisibleParameterAnnotations 方法表中 JDK1.5中新增的属性,作用与RuntimeInvisibleAnnotations类似,只不过作用对象为方法的参数。
    AnnotationDefault 方法表中 JDK1.5中新增的属性,用于记录注解类元素的默认值
    BootstrapMethods 类中 JDK1.7新增的属性,用于保存invokedynamic指令引用的引导方法限定符
    • Code属性

    方法体内的代码经过javac编译之后成为字节码储存在code属性中,接口或者抽象类不存在code属性,

    code属性结构表:

    名称类型数量作用
    attribute_name_index u2 1 CONSTANT_Utf8_info
    attribute_length u4 1  
    max_stack u2 1 操作数栈最大深度,jvm站分配参考
    max_locals u2 1 局部变量表所需的储存空间
    code_length u4 1 字节码长度(jvm中只支持u2长度的函数)
    code u1 code_length 每个指令对应一个u1(最多有256种指令)
    exception_table_length u2 1  
    exception_table exception_info exception_table_length  
    attribute_count u2 1  
    attributes attribute_info attribute_count  

    局部变量表可能引入的参数:

    方法参数,包括this
    显示处理异常的参数
    方法体中定义的局部变量
    ······

    任何实例方法中都会有一个this参数指向方法所属对象,在虚拟机调用实际方法的时候将this传进去。局部变量表中也会有预留出一个变量槽存放对象实例的引用。而static方法中没有该参数。

异常表格式:

 

类型名称数量
u2 start_pc 1
u2 end_pc 1
u2 handler_pc 1
u2 catch_type 1

当从 start_pc行(相对于方法体的偏移量)到end_pc行(不包括)如果出现了catch_type(指向一个CONSTANT_Class_info)或者其子类的异常,则转到handler_pc行进行处理。

注意:每段分支之后的代码都将用finally代码重复一次,比如try之后加一段finally代码,catch之后也会有一段finally代码。

  • Exeptions属性

与Code属性平级,,列出方法中可能会出现的受查异常。也就是throws抛出的异常。

名称类型数量
attribute_name_index u2 1
attribute_length u4 1
number_of_exceptions u2 1
exception_index_table u2 number_of_exceptions
  • LIneNumberTable属性

描述源码行号与字节码行号的属性。不是默认的属性,可以使用-g:none或者-g:lines关闭。

名称类型数量
attribute_name_index u2 1
attribute_length u4 1
line_number_table_length u2 1
line_number_table line_number_info line_number_table_length

line_number_info包含start_pc和line_number两个u2类型的数据项,前者是字节码行号,后者是源码行号。

如果关闭了抛出异常的时候就不会显示抛出异常的行号。

 

  • LocalVariableTable和LocalVariableTypeTable属性

LocalVariableTable描述栈帧中局部变量表与Java源码中定义的变量的关系,其中也有包括改变了在字节码中作用域范围(用start_pc与length一同决定)

LocalVariableTypeTable可以用作记录泛型类型的一个属性。记录局部变量表中的泛型

 

  • SourceFile与SourceDebugExtension属性

SourceFile记录生成这个class文件源码文件的名称

一个类最多存在一个SourceDebugExtension属性,用于额外的代码调试信息。

 

  • ConstantValue属性

通知虚拟机自动为静态变量赋值。

  1. 对于非static的变量在实例构造器<init>中赋值

  2. Oracle实现的机制是:

    1. 同时使用static和final修饰。并且数据类型是String或者基本数据类型的化就是用ConstantValue来初始化。

    2. 没有被final修饰或者是其他类型会选择在<clinit>中初始化。

     

  • InnerClass属性

    InnerClass属性用于记录内部类与宿主类之间的关联

    名称类型数量
    attribute_name_index u2 1
    attribute_length u4 1
    number_of_class u2 1
    inner_classes inner_classes_info number_of_class

    number_of_classes代表记录了多少个内部类信息,每一个内部类信息都由一个inner_classes_info表进行描述。inner_classes_info结构为:

    名称类型数量
    inner_class_info_index u2 1
    outer_class_info_index u2 1
    inner_name_index u2 1
    inner_class_access_flags u2 1

 

  • Deprecated和Synthetic属性

只存在有和没有的区别

通过@Deprecate注解使用,代表某个类、字段或者方法已经被弃用

Synthetic是由编译器自行添加的,生成源代码中的一些不存在的方法、字段甚至是整个类,通过这种方式实现了越权发个文。比如枚举类中自动生成的枚举元素数组和嵌套类的桥接方式,所有不属于用户产生的类、方法、字段都应该设置Synthetic,除了init和clinit方法。

 

  • StackMaptable属性

在类加载的验证作用,在编译期间将一系列的验证类型直接记录在class文件中,通过检验这些验证类型从而省去了类型推导的过程,提升字节码的验证能力,一个方法的code属性最多有一个StackMapTable类型,没错,该类型用以修饰Code属性

 

  • Signature

可选的定长属性,可出现在类、字段、方法表的属性中。

泛型签名如果包含了类型变量或参数化类型,SIgnature会为他记录泛型类型签名信息

  • BootstrapMethods

保存invokedynamic指令引用的引导方法限定符,常量池中如果出现CONSTANT_InvokeDynamic_info类型的变量那么后面必须有BootstarpMethods属性(只能有一个)

 BootstrapMethods_attribute {
     u2 attribute_name_index;
     u4 attribute_length;
     u2 num_bootstrap_methods;
    {   u2 bootstrap_method_ref;//必须是一个CONSTANT_MethodHandle_info结构
         u2 num_bootstrap_arguments;
         u2 bootstrap_arguments[num_bootstrap_arguments];
    } bootstrap_methods[num_bootstrap_methods];
 }

 

  • MethodParameters

记录方法中各个形参的名称信息,可以通过反射api获取。

  • 模块化相关的属性

包括Module、ModulePackages、ModuleMainClass三个属性用来支持java的模块化属性。

  1. Module: 除了该模块的名称、版本、标志信息、模块requires、exports、opens、uses、provides定义的全部内容

  2. ModulePackages 描述该模块中所有的包

  3. ModuleMainClass 描述该模块的主类

  • 运行时注解的相关属性

    例如RuntimeVisibleAnnotations记录了类、字段、方法的声明上记录运行时可见的注解。

 

字节码指令简介

byteshortintlongdoublefloatcharreference
bi si i l d f ci a

 

  • 很多Boolean、char、byte、short类型的操作实际上使用int的指令操作的

  • 加载和储存指令

    1. 将局部变量加载到操作栈:iload、iload_<n>(代表iload0,iload1--iloadn这几个指令)……

    2. 将一个数值从操作数栈储存到局部变量表:istore、istore_n……

    3. 将一个常量加载到操作数栈:bipush、ldc、ldc_w、aconst_null……

    4. 扩充局部变量表的访问索引的指令:wide

  • 运算指令:

    1. xadd

    2. xsub

    3. xmul

    4. xdiv

    5. 求余:xrem

    6. 取反:xneg

    7. 位移指令:xshl

    8. 按位或指令:xor

    9. 按位与指令:xand

    10. 按位异或指令:xxor

    11. 局部自增指令:xinc

    12. 比较指令:xcmpg、xcmpl

  • 类型转换指令: 强制:x2y

  • 对象创建指令

  • 操作数栈管理指令

  • 控制转移指令

  • 方法调用和返回指令

  • 异常指令

  • 同步指令

https://www.jianshu.com/p/e6d4e0f01228

 

 

 

 

posted @ 2022-03-14 22:42  码虫垒起代码之城  阅读(212)  评论(0编辑  收藏  举报