$2.2、java字节码文件的组成与实例详解

java语言号称“一处编译,处处运行”,它能“打出”这样的广告,主要是因为两点:1、它运行在虚拟机环境里,不管哪种操作系统,只要安装了jdk的运行环境就行;2、我们今天的主角-字节码文件,jvm提供了字节码规范,它可以解析字节码文件,因此只要符合字节码的语法,那么在jvm中都是能运行,比如现在的scala,Groovy,Kotlin等都是实现了jvm字节码规范的语言。

首先按照惯例,我们简单写一个java代码,然后查看对应的.class文件

public class MyTest1 {

    private int a = 1;

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }
}

通过使用javap -v MyTest.class,生成了关于常量池,方法和字段描述符,类信息等

Classfile /Users/xxx/Documents/workspace/Jvm/target/classes/jp/zhanng/bytecode/MyTest1.class
  Last modified 2019-12-21; size 481 bytes
  MD5 checksum fc01e439a20da18c0c4ece664cc129ab
  Compiled from "MyTest1.java"
public class jp.zhang.bytecode.MyTest1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#21         // jp/zhang/bytecode/MyTest1.a:I
   #3 = Class              #22            // jp/zhang/bytecode/MyTest1
   #4 = Class              #23            // java/lang/Object
   #5 = Utf8               a
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Ljp/zhang/bytecode/MyTest1;
  #14 = Utf8               getA
  #15 = Utf8               ()I
  #16 = Utf8               setA
  #17 = Utf8               (I)V
  #18 = Utf8               SourceFile
  #19 = Utf8               MyTest1.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = NameAndType        #5:#6          // a:I
  #22 = Utf8               jp/zhang/bytecode/MyTest1
  #23 = Utf8               java/lang/Object
{
  public jp.zhang.bytecode.MyTest1();
    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_1
         6: putfield      #2                  // Field a:I
         9: return
      LineNumberTable:
        line 7: 0
        line 9: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Ljp/zhang/bytecode/MyTest1;

  public int getA();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field a:I
         4: ireturn
      LineNumberTable:
        line 12: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ljp/zhang/bytecode/MyTest1;

  public void setA(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: iload_1
         2: putfield      #2                  // Field a:I
         5: return
      LineNumberTable:
        line 16: 0
        line 17: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   Ljp/zhang/bytecode/MyTest1;
            0       6     1     a   I
}
SourceFile: "MyTest1.java"

到目前为止,我们应该还是不知道上面的信息是个啥的,下面我们介绍一下jvm的字节码规范,后续我们使用16进制查看工具,具体解析出MyTest1.class文件中的信息,看是否能与上述IDEA反编译的结果一致。

  1. 魔术(u4):固定值:CA FE BA BE
  2. 版本号(u4):前2个是次版本号,后2个是主版本号,jvm会验证当前jdk版本号与编译字节码文件的jdk版本号,如果大于编译使用的额,那么会验证通过,如果小于编译使用的jdk,则会报错。(低版本不能运行高版本文件,但是高版本可以运行低版本)
  3. 常量池(u2+n字节):前2位代表常量池的个数,后续的n则是具体的常量池表/数组,具体规范部分可以查看笔者的字节码的组成及各部分规范
  4. 类的访问控制权限(u2),值是由多个权限并集而成。
  5. 类名(u2),类的全路径信息
  6. 父类名(u2),对应父类的路径信息
  7. 接口(u2+n):u2前2位代表所实现接口的个数,后面n代表对应的接口名
  8. 字段fields(u2+n):前2位代表字段的个数,后面n代表字段的表信息,同样参看规范链接部分
  9. 方法(u2+n):前2位代表方法的个个数,后面n代表方法的描述信息
  10. 附加属性信息(u2+n):附加属性的个数和对应的属性表

以上就完整的介绍了字节码的整体组成部分,现在我们再把一些细节进行介绍,后面我们就可以正式进入解读字节码文件了

  • 常量池:常量池描述了类的很多信息,包含字段,方法,类等描述信息,相当于class文件的资源仓库,常量池主要分2种,分别为字面量和符号引用;字面量就是String字符串、final定义的常量,这种类型因为是不可修改的,所以是字面量,还有种是符号引用,包括了类和接口的信息,字段和字段的描述信息,方法和方法的描述信息。
  • 常量池由常量池个数和常量池表组成,常量的池的个数计算需要-1,因为常量池的个数计算是从索引为1开始的,常量0已结被占用,代表null;在常量表部分,根据规范,其中最开始一定有一个tag标记为哪种常量,对应的长度也是固定的u1;具体查看链接中的规范。
  • 每个字段都有对应的描述信息,用于描述字段的数据类型、方法的参数列表(包含参数的个数,顺序)和返回值,对于基本数据类型,它们的表示为:S--->short,B--->byte,Z-->boolean,I--->int,F-->float,D--->double,C-->char,J-->long,V--->void,L-->d对象,也就是我们常见的:Ljava/lang/Object;(这就是Object类在字节码中的表现形式,对象的表现形式后面有分号)
  • 数组的表示,每一个维度都由“[”表示,以此类推,int[][]的代表形式就是:[[I;String[][]的表示形式就是:[[Ljava/lang/string;注意后面的分号也是代表的一部分
  • 方法的描述,按照方法中参数列表出现的顺序、返回值这样的(表现形式为:小括号(参数列表)返回值 )先后顺序进行表示,此时并没有方法的名称(对应的名称描述是在后续的方法规范中进行指向)

eg:public String getNameByIdAndType(int id,String type),则对应的表现形式为:(I,Ljava/lang/string;)Ljava/lag/string;

有了以上的全局理解后,我们再通过16进制工具打开刚刚生成的MyTest1.class文件

下一篇我们就来一个字节一个字节的读下去,我们也可以字节成为“反编译器”了,哈哈

字节解析字节码文件

发布了12 篇原创文章 · 获赞 1 · 访问量 374

猜你喜欢

转载自blog.csdn.net/weixin_39800596/article/details/103641684
今日推荐