美文网首页
字节码文件(.class)内容解析

字节码文件(.class)内容解析

作者: Wannay | 来源:发表于2021-11-08 17:46 被阅读0次

我一直对.class文件编译之后的内容比较感兴趣,我们现在一起来揭秘吧!

我们编写如下的测试代码,我们想要去看看编译出来的.class文件中究竟包含了什么?

package com.wanna.jdkset.TestString;

public class TestString {
    public static void main(String[] args) {
        StringBuffer buffer = new StringBuffer();
        buffer.append("wanna");
    }
}

这里需要借用到IDEA工具中的jclasslib插件,或者是javap命令去进行反编译。

我们用到的命令是javap -v com.wanna.jdkset.TestString,去进行编译成为字节码,最终得到如下的信息

public class com.wanna.jdkset.TestString
  minor version: 0
  major version: 55
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#23         // java/lang/Object."<init>":()V
   #2 = Class              #24            // java/lang/StringBuffer
   #3 = Methodref          #2.#23         // java/lang/StringBuffer."<init>":()V
   #4 = String             #25            // wanna
   #5 = Methodref          #2.#26         // java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
   #6 = Class              #27            // com/wanna/jdkset/TestString
   #7 = Class              #28            // java/lang/Object
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               LocalVariableTable
  #13 = Utf8               this
  #14 = Utf8               Lcom/wanna/jdkset/TestString;
  #15 = Utf8               main
  #16 = Utf8               ([Ljava/lang/String;)V
  #17 = Utf8               args
  #18 = Utf8               [Ljava/lang/String;
  #19 = Utf8               buffer
  #20 = Utf8               Ljava/lang/StringBuffer;
  #21 = Utf8               SourceFile
  #22 = Utf8               TestString.java
  #23 = NameAndType        #8:#9          // "<init>":()V
  #24 = Utf8               java/lang/StringBuffer
  #25 = Utf8               wanna
  #26 = NameAndType        #29:#30        // append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
  #27 = Utf8               com/wanna/jdkset/TestString
  #28 = Utf8               java/lang/Object
  #29 = Utf8               append
  #30 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuffer;
{
  public com.wanna.jdkset.TestString();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 9: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/wanna/jdkset/TestString;

  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           #2                  // class java/lang/StringBuffer
         3: dup
         4: invokespecial #3                  // Method java/lang/StringBuffer."<init>":()V
         7: astore_1
         8: aload_1
         9: ldc           #4                  // String wanna
        11: invokevirtual #5                  // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
        14: pop
        15: return
      LineNumberTable:
        line 18: 0
        line 19: 8
        line 20: 15
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0  args   [Ljava/lang/String;
            8       8     1 buffer   Ljava/lang/StringBuffer;
}

我们来看开头的一部分

  minor version: 0
  major version: 55
  flags: ACC_PUBLIC, ACC_SUPER

minjor和major这两个主要是和字节码版本相关的。flags主要就是类的修饰符,ACC_SUPER代表这个类有父类,ACC_PUBLIC代表这个类是public修饰的。

我们先截取常量池的一部分,其它暂时不看

   #1 = Methodref          #7.#23         // java/lang/Object."<init>":()V
   #2 = Class              #24            // java/lang/StringBuffer
   #3 = Methodref          #2.#23         // java/lang/StringBuffer."<init>":()V
   #4 = String             #25            // wanna
   #5 = Methodref          #2.#26         // java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
   #6 = Class              #27            // com/wanna/jdkset/TestString
   #7 = Class              #28            // java/lang/Object
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               LocalVariableTable
  #13 = Utf8               this
  #14 = Utf8               Lcom/wanna/jdkset/TestString;
  #15 = Utf8               main
  #16 = Utf8               ([Ljava/lang/String;)V
  #17 = Utf8               args
  #18 = Utf8               [Ljava/lang/String;
  #19 = Utf8               buffer
  #20 = Utf8               Ljava/lang/StringBuffer;
  #21 = Utf8               SourceFile
  #22 = Utf8               TestString.java
  #23 = NameAndType        #8:#9          // "<init>":()V
  #24 = Utf8               java/lang/StringBuffer
  #25 = Utf8               wanna
  #26 = NameAndType        #29:#30        // append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
  #27 = Utf8               com/wanna/jdkset/TestString
  #28 = Utf8               java/lang/Object
  #29 = Utf8               append
  #30 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuffer;

我们可以看到,常量池中的Class的部分主要包括下面这几个,它主要存储的就是我们的Class的相关信息。

   #2 = Class              #24            // java/lang/StringBuffer
   #6 = Class              #27            // com/wanna/jdkset/TestString
   #7 = Class              #28            // java/lang/Object

我们可以看到,这三个Class分别指向了#24#27以及#28,我们再从常量池中找到这三个内容,我们发现它们最终都是Utf8的字符串,而这些字符串的内容就是我们会用到的相关类的全限定名。(其实就是注释中的内容,这应该是javap工具帮我们做的)

我们再来查看Methodref部分,主要包括如下几个

   #1 = Methodref          #7.#23         // java/lang/Object."<init>":()V
   #3 = Methodref          #2.#23         // java/lang/StringBuffer."<init>":()V
   #5 = Methodref          #2.#26         // java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;

第一个Methodref就是#7.#23,查找常量池可以发现它的内容就是java/lang/Object."<init>":()V,我们首先观察这个内容?

  • 1.java/lang/Object很显然就是类的全限定名;
  • 2."<init>":()V呢?首先我们查看常量池中的#23元素,发现它的类型是NameAndType
    • 很显然<init>其实就是构造器方法的名称
    • ()V代表的是什么意思呢?这个玩意叫做方法的Descriptor(描述符),它的作用就是用来描述方法的参数和返回值。()V中的()就代表方法是没有参数的,V就代表了它的返回值是void。
    • 根据以上两点,我们可以发现,JVM中把方法名、参数类型以及返回值类型封装到一个NameAndType对象中。

我们可以发现构造器方法,其实本质上也只是一个方法罢了,只不过方法名叫<init>

第二个Methodref是#2.#23,这个和上一个Methodref很类似。
第三个Methodref是#2.#26,查看常量池可以发现它的内容是java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;,我们也是进行分解:

  • 1.java/lang/StringBuffer.append代表的就是我们调用的StringBuffer的方法的方法名,方法名是使用的类全限定名.方法名的方式去进行标识的。
  • 2.(Ljava/lang/String;)Ljava/lang/StringBuffer;代表的就是append方法的描述符,使用到的方法的参数是Ljava/lang/String;,而该方法的返回值类型是Ljava/lang/StringBuffer;。(其中L代表的是它是一个引用类型)

下面要看的就是方法对应的字节码了

  public com.wanna.jdkset.TestString();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 9: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/wanna/jdkset/TestString;

我们可以看到方法名是com.wanna.jdkset.TestString,方法的描述符是()V,访问标识符是ACC_PUBLIC

stack=1, locals=1, args_size=1说明栈的最大为1,局部变量表的大小是1,参数数量是1。

下面就是方法的主要执行部分的字节码了

         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return

先通过aload_0,把局部变量表中的0号槽位的内容(this)压入栈中,然后使用invokespecial #1去执行Object类的空参构造器方法,然后使用return将构造器的栈帧销毁,回到调用方法的栈帧。

然后就是行号表(建立的是字节码的行号和java代码的行号之间的对应关系)

      LineNumberTable:
        line 9: 0

然后是局部变量表

      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/wanna/jdkset/TestString;

我们可以看到,局部变量表中居然放了个this对象?其实所有非静态方法的局部变量表中的0号槽位(slot)的元素都是this,相当于我们所有非静态方法的第一个参数都是this(你也可以在第一个参数位置写个this,但是你不写编译器帮你做了)。

比如下面两种方式都是完全等价的,如果你写了this,编译器就不会再帮你做了,肯定会往局部变量表的槽位0中放入this,如果你没写,那么编译器自然会帮你完成。

    // method01:
    public void test(Test this, String str) {

    }
    // method02:
    public void test(String str) {

    }

接着来看main方法的字节码

  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           #2                  // class java/lang/StringBuffer
         3: dup
         4: invokespecial #3                  // Method java/lang/StringBuffer."<init>":()V
         7: astore_1
         8: aload_1
         9: ldc           #4                  // String wanna
        11: invokevirtual #5                  // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
        14: pop
        15: return
      LineNumberTable:
        line 18: 0
        line 19: 8
        line 20: 15
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0  args   [Ljava/lang/String;
            8       8     1 buffer   Ljava/lang/StringBuffer;

首先我们可以直观的看到,局部变量表的槽位0位置放的不是this,而是args,槽位1中则是放的就是buffer的引用,其它就没什么说的。

相关文章

  • class(一) 字节码文件结构

    class文件与虚拟机 .class文件内容 常量池解析(重点掌握) 1.Class字节码文件由来 Java能够实...

  • 字节码文件(.class)内容解析

    我一直对.class文件编译之后的内容比较感兴趣,我们现在一起来揭秘吧! 我们编写如下的测试代码,我们想要去看看编...

  • Effect JAVA -机制与原理

    JAVA字节码.Class解析 不论该字节码文件来自何方,由哪种编译器编译,甚至是手写字节码文件,只要符合java...

  • 27-javap指令

    一、解析字节码的意义 javap是JDK自带的反解析工具。它的作用就是根据 Class 字节码文件,反解析出当前类...

  • ClassLoder学习笔记

    ClassLoder 负责加载class文件,class文件在文件开头有特定的文件标识,将class文件字节码内容...

  • class文件字节码解析

    本篇文章将介绍 .class 文件的结构,通过一个简单的例子认识 .class 文件。首先写一个java文件(本人...

  • class字节码文件解析

    文件结构 Java class文件是8位字节的二进制流,数据项按顺序存储在class文件中,相邻的项之间没有任何间...

  • Java 程序运行原理分析

    1. class 文件内容 class 文件包含JAVA程序执行的字节码;数据严格按照格式紧凑排列在class文件...

  • 新鲜出炉,深入讲解java反射的底层原理,这篇算讲的不错了!

    反射 反射 Java代码和Java文件 Java文件和.class字节码文件 class字节码文件在内存中的位置 ...

  • Java字节码结构解析

    本文通过解析Class文件中字节码的结构,来加深对Java类文件结构的理解。建议先阅读Java类文件结构解析这篇文...

网友评论

      本文标题:字节码文件(.class)内容解析

      本文链接:https://www.haomeiwen.com/subject/ijxpzltx.html