ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2021-07-04 23:00:05  阅读:197  来源: 互联网

标签:lang Ljava Java 字节 Utf8 Test 详细分析 常量


文章目录

一、初识字节码

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, 该字段描述方法的属性数量,其值说明该方法有一个属性, 见下面的分析

标签:lang,Ljava,Java,字节,Utf8,Test,详细分析,常量
来源: https://blog.csdn.net/qq_39208832/article/details/118468965

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有