Java字节码文件详细分析(一)

2021/7/5 1:22:42

本文主要是介绍Java字节码文件详细分析(一),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

文章目录

  • 一、初识字节码
    • 1.1 测试用例准备
    • 1.2 编译和反编译
    • 1.3 查看字节码文件的二进制
  • 二、魔数与版本
    • 2.1 魔数
    • 2.2 版本号
  • 三、常量池
    • 3.1 常量池常量数
    • 3.2 常量池的基本结构
      • 3.2.1 JVM所定义的11种常量池元素类型
      • 3.2.2 11种常量池元素类型的具体组成
      • 3.2.3 第一个常量池元素
      • 3.2.4 第二个常量池元素
      • 3.2.5 同理类推
  • 四、访问标识与继承信息
    • 4.1 access_flags
    • 4.2 this_class
    • 4.3 super_class
    • 4.4 interface
      • 4.4.1 interfaces_count
      • 4.4.2 interfaces[interfaces_count]
  • 五、字段信息
    • 5.1 fields_count
    • 5.2 fields_info_fields
  • 六、方法信息
    • 6.1 methods_count
    • 6.2 method_info methods
    • 6.3 第一个方法 void \

一、初识字节码

1.1 测试用例准备

​ 所构造的Java测试类如下:

public class Test {
    public int a = 3;
    static Integer si = 6;
    String s = "Hello,World!";
    public static void main(String[] args) {
        Test test = new Test();
        test.a = 8;
        si = 9;
    }
    private void test (){
        this.a = a;
    }
}

1.2 编译和反编译

使用IDEA进行编译后产生class文件,使用 javap -v Test.class 分析。

λ javap -v Test.class
Classfile /E:/IDEA-Project/JVM/out/production/JVM/Test.class
  Last modified 2021-7-3; size 757 bytes
  MD5 checksum 96cabb2b8e3e791a984abfcd1676ab88
  Compiled from "Test.java"
public class Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #9.#31         // java/lang/Object."<init>":()V
   #2 = Fieldref           #5.#32         // Test.a:I
   #3 = String             #33            // Hello,World!
   #4 = Fieldref           #5.#34         // Test.s:Ljava/lang/String;
   #5 = Class              #35            // Test
   #6 = Methodref          #5.#31         // Test."<init>":()V
   #7 = Methodref          #36.#37        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #8 = Fieldref           #5.#38         // Test.si:Ljava/lang/Integer;
   #9 = Class              #39            // java/lang/Object
  #10 = Utf8               a
  #11 = Utf8               I
  #12 = Utf8               si
  #13 = Utf8               Ljava/lang/Integer;
  #14 = Utf8               s
  #15 = Utf8               Ljava/lang/String;
  #16 = Utf8               <init>
  #17 = Utf8               ()V
  #18 = Utf8               Code
  #19 = Utf8               LineNumberTable
  #20 = Utf8               LocalVariableTable
  #21 = Utf8               this
  #22 = Utf8               LTest;
  #23 = Utf8               main
  #24 = Utf8               ([Ljava/lang/String;)V
  #25 = Utf8               args
  #26 = Utf8               [Ljava/lang/String;
  #27 = Utf8               test
  #28 = Utf8               <clinit>
  #29 = Utf8               SourceFile
  #30 = Utf8               Test.java
  #31 = NameAndType        #16:#17        // "<init>":()V
  #32 = NameAndType        #10:#11        // a:I
  #33 = Utf8               Hello,World!
  #34 = NameAndType        #14:#15        // s:Ljava/lang/String;
  #35 = Utf8               Test
  #36 = Class              #40            // java/lang/Integer
  #37 = NameAndType        #41:#42        // valueOf:(I)Ljava/lang/Integer;
  #38 = NameAndType        #12:#13        // si:Ljava/lang/Integer;
  #39 = Utf8               java/lang/Object
  #40 = Utf8               java/lang/Integer
  #41 = Utf8               valueOf
  #42 = Utf8               (I)Ljava/lang/Integer;
{
  public int a;
    descriptor: I
    flags: ACC_PUBLIC

  static java.lang.Integer si;
    descriptor: Ljava/lang/Integer;
    flags: ACC_STATIC

  java.lang.String s;
    descriptor: Ljava/lang/String;
    flags:

  public Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_3
         6: putfield      #2                  // Field a:I
         9: aload_0
        10: ldc           #3                  // String Hello,World!
        12: putfield      #4                  // Field s:Ljava/lang/String;
        15: return
      LineNumberTable:
        line 1: 0
        line 2: 4
        line 4: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0  this   LTest;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #5                  // class Test
         3: dup
         4: invokespecial #6                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: bipush        8
        11: putfield      #2                  // Field a:I
        14: bipush        9
        16: invokestatic  #7                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        19: putstatic     #8                  // Field si:Ljava/lang/Integer;
        22: return
      LineNumberTable:
        line 7: 0
        line 8: 8
        line 9: 14
        line 10: 22
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      23     0  args   [Ljava/lang/String;
            8      15     1  test   LTest;

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: bipush        6
         2: invokestatic  #7                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         5: putstatic     #8                  // Field si:Ljava/lang/Integer;
         8: return
      LineNumberTable:
        line 3: 0
}
SourceFile: "Test.java"

​ 在IDEA中安装插件 jclasslib,并选择编译产生的class文件,查看。

在这里插入图片描述

1.3 查看字节码文件的二进制

​ 使用HxD工具打开class文件。
在这里插入图片描述

二、魔数与版本

2.1 魔数

​ 所有.class字节码文件的开始的4个字节代表魔数,并且其值一定为 0xCAFE BABE (咖啡宝贝 =。=)

​ 如果开始的4个字节不是CAFE BABE, 则 JVM 将会认为该文件不是 .class 字节码文件,会拒绝解析。

在这里插入图片描述

2.2 版本号

​ 在魔数后面紧跟的4个字节分别为 次版本号主版本号

在这里插入图片描述

​ 可以看到这里的次版本号为0,主版本号为 00 34 即对应十进制的52。

JDK versionmajor主版本号minor 次版本号
1.0443
1.1453
1.2460
1.3470
1.4480
1.5490
1.6500
1.7510
1.8520

​ 可以利用 jclasslib插件查看:

在这里插入图片描述

三、常量池

3.1 常量池常量数

在这里插入图片描述

​ 后面2个字节代表常量池常量数,00 2B 对应十进制的 43;但是根据 javap 反编译后的文件( 上面的1.2 节 ),由于索引是从0开始的,但所有的字节码文件0位索引代表null,不显示出来,因此能看到的常量池数是 43-1 = 42个:

Constant pool:
   #1 = Methodref          #9.#31         // java/lang/Object."<init>":()V
   #2 = Fieldref           #5.#32         // Test.a:I
   #3 = String             #33            // Hello,World!
   #4 = Fieldref           #5.#34         // Test.s:Ljava/lang/String;
   #5 = Class              #35            // Test
   #6 = Methodref          #5.#31         // Test."<init>":()V
   #7 = Methodref          #36.#37        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #8 = Fieldref           #5.#38         // Test.si:Ljava/lang/Integer;
   #9 = Class              #39            // java/lang/Object
  #10 = Utf8               a
  #11 = Utf8               I
  #12 = Utf8               si
  #13 = Utf8               Ljava/lang/Integer;
  #14 = Utf8               s
  #15 = Utf8               Ljava/lang/String;
  #16 = Utf8               <init>
  #17 = Utf8               ()V
  #18 = Utf8               Code
  #19 = Utf8               LineNumberTable
  #20 = Utf8               LocalVariableTable
  #21 = Utf8               this
  #22 = Utf8               LTest;
  #23 = Utf8               main
  #24 = Utf8               ([Ljava/lang/String;)V
  #25 = Utf8               args
  #26 = Utf8               [Ljava/lang/String;
  #27 = Utf8               test
  #28 = Utf8               <clinit>
  #29 = Utf8               SourceFile
  #30 = Utf8               Test.java
  #31 = NameAndType        #16:#17        // "<init>":()V
  #32 = NameAndType        #10:#11        // a:I
  #33 = Utf8               Hello,World!
  #34 = NameAndType        #14:#15        // s:Ljava/lang/String;
  #35 = Utf8               Test
  #36 = Class              #40            // java/lang/Integer
  #37 = NameAndType        #41:#42        // valueOf:(I)Ljava/lang/Integer;
  #38 = NameAndType        #12:#13        // si:Ljava/lang/Integer;
  #39 = Utf8               java/lang/Object
  #40 = Utf8               java/lang/Integer
  #41 = Utf8               valueOf
  #42 = Utf8               (I)Ljava/lang/Integer;

3.2 常量池的基本结构

  • ​ java类所对应的常量池主要由常量池数量常量池数组两部分组成, 常量池数组紧跟在常量池数量后面

  • ​ 常量池数组中的不同的元素的类型,结构都是不同的,长度也是不同的

  • ​ 但是每一种元素的第一个数据都是一个u1类型 ( 1字节 ),该字节是标志位;JVM 解析常量池时依靠一个标志位来获取该元素的具体类型;

在这里插入图片描述

3.2.1 JVM所定义的11种常量池元素类型

在这里插入图片描述

3.2.2 11种常量池元素类型的具体组成

​ 常量池的每一种元素类型都是复合的数据结构类型,下面分别给出 JVM 所定义的常量池中每一种元素的具体结构。

在这里插入图片描述

3.2.3 第一个常量池元素


在这里插入图片描述

​ 即对应

  #1 = Methodref          #9.#31         // java/lang/Object."<init>":()V
  ...
  #9 = Class              #39            // java/lang/Object
  ...
  #16 = Utf8               <init>
  #17 = Utf8               ()V
  #31 = NameAndType        #16:#17        // "<init>":()V

​ 常量池的第一个元素的方法引用常量,引用的方法来自java/lang/Object这个类(#9 第9号常量池元素), 方法名和类型为(#31 第31号常量池元素), 可以看到 方法名和类型 指向#31号常量池元素,但是该元素又指向#16和#17, 同理可得#16项常量池元素为方法名,#17号常量池元素为类型; 即方法名为,类型为 ()V , 其中()为入参,括号内为空表示没有入参,V代表方法的返回类型为void。

在这里插入图片描述

​ 即对应:

在这里插入图片描述

3.2.4 第二个常量池元素

在这里插入图片描述

​ 第二个元素是 字段符号引用常量:
在这里插入图片描述

其指向的所属类名是 #5:Test类 , 指向的字段描述符(字段名字和类型)为 #32:a和I, 其中a为字段名,I为类型代表整数类型;

3.2.5 同理类推

在这里插入图片描述

​ 当最后一个常量池元素结束时,class字节码文件对应到:

在这里插入图片描述

四、访问标识与继承信息

4.1 access_flags

​ 在字节码文件中,常量数组之后紧跟着的是 access_flags 结构,该结构的类型是u2(2字节), access_flags 代表访问标志位,该标志用于标注类或接口层次的访问信息,例如 描述当前类是类还是接口,是否定义为public类型,是否定义为abstract类型等。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

​ 由于 Test.class 中的 access_flags=0x0021,因此该类的访问表标识既包含了 ACC_PUBLIC 也包含了 ACC_SPUER ;

4.2 this_class

​ 在字节码文件中, 紧跟 access_flags 访问标识之后的是 this_class 结构, 该结构是u2类型(2字节);

this_class 记录当前类的全限定名(包名+类名), 其值指向常量池中对应的索引值;

在这里插入图片描述

​ 指向#5号常量池

  #5 = Class              #35            // Test

4.3 super_class

​ 在字节码文件中, 紧跟 this_class 访问标识之后的是 super_class 结构, 该结构是u2类型(2字节);

this_class 记录当前类父类的全限定名(包名+类名), 其值指向常量池中对应的索引值;

在这里插入图片描述

指向#9号常量池

  #9 = Class              #39            // java/lang/Object

4.4 interface

4.4.1 interfaces_count

​ 在字节码文件中, 紧跟 super_class 访问标识之后的是 interfaces_count 结构, 该结构是u2类型(2字节);

interfaces_count 记录当前类所实现的接口数量;

在这里插入图片描述

4.4.2 interfaces[interfaces_count]

​ 在字节码文件中, 紧跟 interfaces_count 访问标识之后的是 interfaces[interfaces_count] 结构, 表示一组接口索引集合,是一组u2类型数据的集合(即每个接口使用2字节来索引常量池元素的标号),这些被实现的接口将按 implements语句(如果该类为接口,则为 extend 语句) 后的接口顺序从左至右排列在接口的索引集合中;

由于Test.class的interfaces_count值为0,因此字节码文件中并没有interfaces信息

五、字段信息

5.1 fields_count

​ 在字节码文件中, 紧跟 interfaces 访问标识之后的是 fields_count 结构, 该结构是u2类型(占2个字节)。 该值记录当前类中所定义的变量总数,包括类成员变量和类变量(即静态变量);

在这里插入图片描述

​ 即对应:

在这里插入图片描述

5.2 fields_info_fields

​ 在字节码文件中, 紧跟 fields_count 字段信息之后的是 fields_info_fields 结构, 该结构长度不确定,不用的变量类型所占的长度是不同的。

fileds 记录类中所定义的各个变量的详细信息,包括变量名,变量类型,访问标识和属性等;

1. fields的组成结构格式

在这里插入图片描述

	2. **变量access_flags结构**

在这里插入图片描述

在这里插入图片描述

​ 根据字节码文件,可以看到第一个域变量为 public, 变量名指向常量池#10,变量类型等描述指向常量池#11,并且属性数量为0, 因此并没有属性信息字段;

​ 对应可以看:

在这里插入图片描述

需要注意的是类型的标识字符与类型的关系如下:

在这里插入图片描述

​ 同理可得其他2个变量,可以分析得到:

在这里插入图片描述

​ 对应到

在这里插入图片描述

六、方法信息

6.1 methods_count

​ 在字节码文件中, 紧跟字段信息之后的是方法信息;首先的methods_count结构,该结构是u2类型(占2字节)。

该结构描述类中的一共包含多少方法;

在这里插入图片描述

​ 由上图分析可得,类中共有4个方法。但是类中明明就只有2个方法, 一个test方法,一个main方法。

需要注意的是

1. 类中虽然没有写构造函数,但是编译器会自动为该类添加一个默认的构造函数
2. 编译期间,编译器会自动为该类增加 一个 `void <clinit>()` 方法, 其方法名就是 **<clinit>** ,返回值为 void;
3. <clinit>方法作用主要是执行类的初始化,源程序中的所有**static类型**的变量都会在这个方法中完成初始化,全部被 **static {}**

语句块所包围的程序都在这个方法中执行;

6.2 method_info methods

​ 紧跟着methods_count结构的是 methods结构, 这是一个数组, 每一个方法的全部细节都包含在里面, 包括代码指令

  1. methods结构的组成格式

在这里插入图片描述

在这里插入图片描述

6.3 第一个方法 void <init>

在这里插入图片描述

0x0001:access_flags, 说明该方法的访问属性为 public

0x0010:name_index, 该字段描述的是方法名, 其值指向常量池对应的元素编号, 对应#16

#16 = Utf8     <init>

**0x0011:descriptor_index,该字段描述的是方法的入参和出参信息, 其值指向常量池对应的元素编号,对应#17 **

#17 = Utf8               ()V ;; 说明入参为空,返回类型为void

0x0001:attributes_count, 该字段描述方法的属性数量,其值说明该方法有一个属性, 见下面的分析



这篇关于Java字节码文件详细分析(一)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程