JVM字节码文件结构深度剖析

发布于:2025-06-21 ⋅ 阅读:(21) ⋅ 点赞:(0)

反汇编,以下命令可以查看相对可读的详细结构

javap -verbose ByteCode.class

与Class二进制文件并不是直接对齐的

Class二进制文件结构参照表

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;字段数量
    field_info     fields[fields_count];字段信息表
    u2             methods_count;方法数量
    method_info    methods[methods_count];方法信息表
    u2             attributes_count;附加属性数量
    attribute_info attributes[attributes_count];附加属性表
}

在这里插入图片描述

常量池入口,占用二个字节,常量池中的第0个位置被我们的jvm占用了表示为null 所以我们通过编译出来的常量池索引是从1开始的。

常量池是 class 文件中一个用于存放各种**字面量(如字符串、数值)和符号引用(类名、字段、方法名等)**的表。

常量池常量结构表

u1,u2,u4,u8分别代表1个字节,2个字节,4个字节,8个字节的无符号数

tag均是u1

tag 常量类型 名称 结构说明 示例
1 CONSTANT_Utf8 UTF-8 编码字符串 u2 length + u1[length] bytes:MUTF-8 编码的字符串 “Hello”, “main”, “(I)V”
3 CONSTANT_Integer 整型常量 u4 bytes:32 位整型值 100-1
4 CONSTANT_Float 浮点型常量 u4 bytes:IEEE 754 float 3.14f
5 CONSTANT_Long 长整型常量 u4 high_bytes + u4 low_bytes(注意占用两项) 123456789012345L
6 CONSTANT_Double 双精度浮点常量 u4 high_bytes + u4 low_bytes(注意占用两项) 3.1415926535
7 CONSTANT_Class 类或接口名 u2 name_index:指向一个 Utf8 常量 java/lang/String
8 CONSTANT_String 字符串字面量引用 u2 string_index:指向一个 Utf8 常量 "Hello"
9 CONSTANT_Fieldref 字段引用 u2 class_index + u2 name_and_type_index System.out
10 CONSTANT_Methodref 方法引用 u2 class_index + u2 name_and_type_index println(String)
11 CONSTANT_InterfaceMethodref 接口方法引用 u2 class_index + u2 name_and_type_index Comparable.compareTo(Object)
12 CONSTANT_NameAndType 名字和类型描述符组合,可以是字段名+类型,也可以是方法名+方法描述符 u2 name_index + u2 descriptor_index <init>:()V, add:(II)I

13456属于字面量、其他属于符号引用类型

我们的常量池可以看作我们的java class类的一个资源仓库(比如Java类定的方法和变量信息),我们后面的方法类的信息的描述信息都是通过索引去常量池中获取。

  1. 常量池中主要存放二种常量,一种是字面量 一种是符号引用
    在这里插入图片描述
    在这里插入图片描述

在JVM规范中,每个字段或者变量都有描述信息,描述信息的主要作用是数据类型,方法参数列表,返回值类型等。 基本参数类型和void类型都是用一个大写的字符来表示,对象类型是通过一个大写L加全类名表示,这么做的好处就是在保证jvm能读懂class文件的情况下尽量的压缩class文件体积.

  1. 基本数据类型表示:
  • B---->byte
  • C---->char
  • D---->double
  • F----->float
  • I------>int
  • J------>long
  • S------>short
  • Z------>boolean
  • V------->void
  1. 对象类型:
  • String------>Ljava/lang/String;(前面有个L后面有一个分号)
  1. 对于数组类型: 每一个唯独都是用一个前置[来表示比如:
  • int[] ------>[I
  • String [][]------>[[Ljava.lang.String;

用描述符来描述方法的,先参数列表,后返回值的格式,参数列表按照严格的顺序放在()中
比如String getUserInfoByIdAndName(int id,String name) 的方法描述符号

  • (I,Ljava/lang/String;)Ljava/lang/String;

Class文件结构类的访问标识符号解析 Access_flag

标志位(hex) 标志名 含义
0x0001 ACC_PUBLIC 类是 public
0x0010 ACC_FINAL 类是 final(不可继承)
0x0020 ACC_SUPER 使用了新的 invokespecial 语义(Java 1.0.2+)
0x0200 ACC_INTERFACE 是接口
0x0400 ACC_ABSTRACT 是抽象类
0x1000 ACC_SYNTHETIC 编译器生成的类
0x2000 ACC_ANNOTATION 是注解(Annotation)
0x4000 ACC_ENUM 是枚举类

jvm规范并没有穷举出所以的类型 而是通过位运算的出来的。
0x0021 = 0x0020 位运算 0x0001 那么我们可以得出这个class的访问权限是ACC_PUBLIC 和ACC_SUPER

This class name的描述当前的所属类

this class name 占用二个字节,表示索引

super class name (当前class的父类名字)

同样占用二个字节,也是表示索引值

接口信息

实现的接口数量

占用二个字节表示实现了几个接口,是实现接口数量的上限也是0xffff

实现的接口

每个接口名占两个字节,来表示接口的位于常量池中的索引

字段表信息分析

字段结构

field_info {
    u2 access_flags;         // 访问标志(如 public、static、final 等)
    u2 name_index;           // 字段名称(指向常量池的 Utf8 项)
    u2 descriptor_index;     // 字段类型描述符(指向常量池的 Utf8 项)
    u2 attributes_count;     // 属性数量
    attribute_info attributes[attributes_count]; // 属性表(如 ConstantValue 等)
}

field_info.attributes一般会存:

  • ConstantValue
    只有 static final 常量字段才有,存储该字段的常量值(比如 public static final int MAX = 100;),内容是一个指向常量池中具体常量的索引。
  • Deprecated
    标记字段已经废弃,提示编译器或工具警告。
  • Synthetic
    标记字段是编译器生成的,不是源代码写的。
  • RuntimeVisibleAnnotationsRuntimeInvisibleAnnotations
    存储字段上的注解信息。

字段访问标志

标志位(hex) 标志名 含义
0x0001 ACC_PUBLIC 字段是 public
0x0002 ACC_PRIVATE 字段是 private
0x0004 ACC_PROTECTED 字段是 protected
0x0008 ACC_STATIC 字段是 static
0x0010 ACC_FINAL 字段是 final
0x0040 ACC_VOLATILE 字段是 volatile
0x0080 ACC_TRANSIENT 字段是 transient
0x1000 ACC_SYNTHETIC 编译器生成的字段
0x4000 ACC_ENUM 枚举类型字段

方法表信息分析

方法结构

method_info {
    u2 access_flags;           // 访问标志(public、static、final等)
    u2 name_index;             // 方法名索引(指向常量池)
    u2 descriptor_index;       // 方法描述符索引(参数类型和返回值)(()V 表示的是无参无返)
    u2 attributes_count;       // 属性个数
    attribute_info attributes[attributes_count]; // 属性数组(如Code属性)
}

method_info.attributes一般会存:

属性名 说明
Code 最核心: 存放方法的字节码指令、栈帧信息等(非 abstract/native 方法必须有)
Exceptions 声明该方法可能抛出的异常列表(即 throws
LineNumberTable 源代码行号到字节码地址的映射(调试用)
LocalVariableTable 局部变量名、类型、作用域(调试用)
RuntimeVisibleAnnotations 运行时可见注解
RuntimeInvisibleAnnotations 编译期注解,运行时不可见
Synthetic 标记该方法是编译器生成的,不是源代码写的
Deprecated 标记该方法已过时
Signature 泛型信息描述(如果用了泛型)
MethodParameters 记录参数名(从Java 8开始)

方法访问标志

标志位(hex) 标志名 含义
0x0001 ACC_PUBLIC 方法是 public
0x0002 ACC_PRIVATE 方法是 private
0x0004 ACC_PROTECTED 方法是 protected
0x0008 ACC_STATIC 方法是 static
0x0010 ACC_FINAL 方法是 final
0x0020 ACC_SYNCHRONIZED 方法是 synchronized
0x0040 ACC_BRIDGE 编译器生成的桥接方法
0x0080 ACC_VARARGS 方法使用可变参数
0x0100 ACC_NATIVE 方法是本地方法(native)
0x0400 ACC_ABSTRACT 抽象方法
0x0800 ACC_STRICT 使用严格浮点数计算
0x1000 ACC_SYNTHETIC 编译器生成的方法

attribute_info

attribute_info {
    u2 attribute_name_index;//attribute名称常量池索引
    u4 attribute_length;//info长度
    u1 info[attribute_length];//具体数据
}
  • 类级别属性(ClassFile 末尾的 attributes[])
    • SourceFile_index
              SourceFile_attribute {
                  u2 attribute_name_index;   // 常量池中 "SourceFile" 字符串的索引
                  u4 attribute_length;       // 属性长度,固定为2
                  u2 sourcefile_index;       // 指向常量池中一个 Utf8 字符串,表示源文件名(如 "Test.java")
          }
      
  • 字段级别属性(field_info 中的 attributes[])
    • ConstantValue
      用于修饰 静态字段(static final),表示该字段的编译时常量值,比如基本类型的字面量(int、long、float、double、String)。JVM 加载类时,会将这个值赋给对应的静态变量。
          ConstantValue_attribute {
              u2 attribute_name_index;    // 指向常量池中 "ConstantValue" 的 Utf8
              u4 attribute_length;        // 固定为 2
              u2 constantvalue_index;     // 指向常量池中该字段的常量值(Integer、Float、Long、Double、String)
          }
      
  • 方法级别属性(method_info 中的 attributes[])
    • code

      Code_attribute {
          u2 attribute_name_index;     // 必然指向 "Code"
          u4 attribute_length;         // 属性总长度(不含前6字节)
          u2 max_stack;                // 操作数栈最大深度
          u2 max_locals;              // 局部变量槽个数
          u4 code_length;              // 字节码长度(表示这个code[] 数组的字节数)
          u1 code[code_length];        // 字节码指令序列
          u2 exception_table_length;   // 异常处理表长度
          {
              u2 start_pc;
              u2 end_pc;
              u2 handler_pc;
              u2 catch_type; // 指向常量池(异常类),0代表 catch all
          } exception_table[exception_table_length];
          u2 attributes_count;//属性表的个数
          attribute_info attributes[attributes_count]; // Code 的子属性,比如 LineNumberTable 等
      }
      

      code[length]中存的是字节码指令助记符号(JVM执行的机器码)

    • LineNumberTable
      主要作用是 记录字节码指令与源代码行号的对应关系。便调试器定位当前执行的代码行,实现断点、单步调试等功能。

          LineNumberTable_attribute {
              u2 attribute_name_index;       // 常量池中 "LineNumberTable" 的索引
              u4 attribute_length;           // 属性长度
              u2 line_number_table_length;   // 表项个数(有些人对这个属性名定义不同,但是是同一个东西)
              {
                  u2 start_pc;               // 字节码偏移量(指令起始位置)
                  u2 line_number;            // 源代码行号
              } line_number_table[line_number_table_length];
          }
      

      line_number_table_length+line_number_table[line_number_table_length]=info[attribute_length]

    • LocalVariableTable

          LocalVariableTable_attribute {
              u2 attribute_name_index;       // 常量池中 "LocalVariableTable" 字符串的索引
              u4 attribute_length;           // 属性长度
              u2 local_variable_table_length; // 局部变量表项数
              {
                  u2 start_pc;               // 变量作用域开始的字节码偏移(相当于哪个字节码指令开始可以使用这个变量)
                  u2 length;                 // 作用域长度(字节码偏移范围)
                  u2 name_index;             // 变量名,指向常量池Utf8
                  u2 descriptor_index;       // 变量类型描述符,指向常量池Utf8
                  u2 index;                  // 局部变量表槽号
              } local_variable_table[local_variable_table_length];
          }
      

      local_variable_table_length+local_variable_table[local_variable_table_length]=info[attribute_length]

    • MethodParameters
      编译器(JDK 8+)如果开启 -parameters 参数,会在字节码里生成这个属性,保留方法参数名。反射时调用 Method.getParameters() 可以获得真实的参数名。

          MethodParameters_attribute {
              u2 attribute_name_index;
              u4 attribute_length;
              u1 parameters_count;
              {
                  u2 name_index;     // 参数名(常量池Utf8索引)
                  u2 access_flags;   // 参数修饰符,如 final, synthetic, mandated
              } parameters[parameters_count];
          }
      

      parameters_count+parameters[parameters_count=info[attribute_length]


网站公告

今日签到

点亮在社区的每一天
去签到