关于Java字节码文件的认识

首先准备材料:

1 Winhex工具下载:http://www.x-ways.net/winhex/
Winihex用来查看class文件
2 JclassLib工具下载:https://github.com/ingokegel/jclasslib
用于结构化展现字节码文件
3 java命令:javap -C 类文件名/java -verbose 类文件名

D:\zhunode_prac\demo\target\classes\com\zhunode\bytecode>javap -verbose MyTest01.class
Classfile /D:/zhunode_prac/demo/target/classes/com/zhunode/bytecode/MyTest01.class
  Last modified 2019-6-8; size 481 bytes
  MD5 checksum dc8c25bf7aa8c02cf95d1dc625ae5ee8
  Compiled from "MyTest01.java"
public class com.zhunode.bytecode.MyTest01
  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         // com/zhunode/bytecode/MyTest01.a:I
   #3 = Class              #22            // com/zhunode/bytecode/MyTest01
   #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               Lcom/zhunode/bytecode/MyTest01;
  #14 = Utf8               getA
  #15 = Utf8               ()I
  #16 = Utf8               setA
  #17 = Utf8               (I)V
  #18 = Utf8               SourceFile
  #19 = Utf8               MyTest01.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = NameAndType        #5:#6          // a:I
  #22 = Utf8               com/zhunode/bytecode/MyTest01
  #23 = Utf8               java/lang/Object
{
  public com.zhunode.bytecode.MyTest01();
    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 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/zhunode/bytecode/MyTest01;

  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 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/zhunode/bytecode/MyTest01;

  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 9: 0
        line 10: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   Lcom/zhunode/bytecode/MyTest01;
            0       6     1     a   I
}
SourceFile: "MyTest01.java"

4 准备java文件:com.zhunode.MyTest01.java文件

package com.zhunode.bytecode;

public class MyTest01 {
    private int a;
    
    public int getA() {
        return this.a;
    }
    public void setA(int a) {
        this.a = a;
    }
}

5 常量表数据结构描述符
在这里插入图片描述
关于常量表数据结构:可以查看《Java虚拟机规范》,本章是来自Java虚拟机8,来自第四章内容。当然,关于JVM虚拟机规范也可以查看官方文档。

第二部分:开始查看文件:

利用Winhex工具打开class文件,内容如图:
在这里插入图片描述
现在来逐步认识这份字节码结构:
首先该文件是一个16进制的文件
在这里插入图片描述
对于第一行的内容来说,CA FE BA BE 是一个固定值,CAFE,JAVA标志,BABE,baby。这个就是魔数,用于标识该文件是Java的二进制文件,JVM在加载class文件时,会通过魔数来检查该文件是否为java class文件。
跟着CA FE BA BE 后面的 00 00 00 34 表示小版本号和大版本号,如00 就与下图的minor version对应,00 34就与major version对应
在这里插入图片描述

00 00转为10进制=0
00 34转为10进制=3*16^1 +  4*16^0 = 52 

在说常量池之前,需要注意的是,常量池不是用于存储常量的,它是一个数据资源库,用于为后面的方法表等部分的内容提供资源内容。

后面的00 18 表示常量池的个数:转为10进制的数值为:24。表示常量池中常量的个数为24,不过因为常量池中有一个常量为保留常量,即JVM内部定义的一个常量,所以除去这个常量,常量池中的常量的个数为24 - 1 = 23。下图就是通过javap -verbose得到的常量池
在这里插入图片描述
常量池的个数已经知道了,那么接下来的就是常量池的内容了。根据 材料准备 的 常量池数据结构信息可知,tag项用于表示
常量类型,一个用1bit表示。那么紧接着 00 18的 0A表示tag值为10 ,那么对应的常量类型为:Constant FieldRef info,这部分由三部分内容组成,1bit的index,2bit的用于指向Constant_Class_info类型的索引,2bit用于指向Constant_NameAndType类型的索引。那么就可以确定第一个常量池中的常量值内容了:0A 00 00 04 00 14该部分就用于描述常量池的第一个常量。
与第一个常量类似:接下来就直接画出所有的常量的二进制标志了。
在这里插入图片描述

序号二进制表示释义对应的常量
10A 00 04 00 1400 004 表示第一个index,00 14表示第二个index0A对应常量类型:CONSTANT_Methodref_info,详情见常量信息。
209 00 03 00 1500 03表示第一个index,指向声明的字段的类或者是Constant_Class_info的索引项,即索引项为3;00 15表示第二个index,指向字段的描述符的Constant_TypeAndName_info的索引项,索引项为2109对应的常量类型:Constant FieldRef info
307 00 1600 16表示索引项,指的是指向常量限定名索引,索引项为2207对应的常量类型:Constant_Class_Info
407 00 1700 17表示索引项,指的是指向常量限定名索引,索引项为2307对应的常量类型:Constant_Class_Info
501 00 01 6100 01指的是UTF8编码的字符串占用的字符数,61 指长度为length的UTF-8编码的字符串,转为10进制数值为97 ,表示的数值为a。01对应的常量类型:Constant_UTF8_Info
601 00 01 4900 01指的是UTF8编码的字符串占有的字符数,只有一个字符串,49 表示的字符数值为I。01对应的常量类型:Constant_UTF8_Info
701 00 06 3C 69 6E 69 74 3E00 06指的是UTF8编码的字符串占有的字符数为6,有6个字符,3C 69 6E 69 74 3E 表示的字符数值为<init>。01对应的常量类型:Constant_UTF8_Info
801 00 03 28 29 5600 03指的是UTF8编码的字符串占有的字符数为3,有3个字符,28 29 56表示的字符数值为()V。01对应的常量类型:Constant_UTF8_Info
901 00 04 43 6F 64 6500 04指的是UTF8编码的字符串占有的字符数为4,有4个字符,43 6F 64 65表示的字符数值为Code。01对应的常量类型:Constant_UTF8_Info
1001 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 6500 0F指的是UTF8编码的字符串占有的字符数为16,有16个字符,4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65表示的字符数值LineNumberTable。01对应的常量类型:Constant_UTF8_Info
1101 00 12 4C 4F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 6500 12指的是UTF8编码的字符串占有的字符数为18,有18个字符,4C 4F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65表示的字符数值LocalVariableTable01对应的常量类型:Constant_UTF8_Info
1201 00 04 74 68 69 7300 04指的是UTF8编码的字符串占有的字符数为4,有4个字符,74 68 69 73表示的字符数值this01对应的常量类型:Constant_UTF8_Info
1301 00 1F 4C 63 6F 6D 2F 7A 68 75 6E 6F 64 65 2F 62 79 74 65 63 6F 64 65 2F 4D 79 54 65 73 74 30 31 3B00 1F指的是UTF8编码的字符串占有的字符数为32,有32个字符,4C 63 6F 6D 2F 7A 68 75 6E 6F 64 65 2F 62 79 74 65 63 6F 64 65 2F 4D 79 54 65 73 74 30 31 3B表示的字符数值Lcom/zhunode/bytecode/MyTest01;01对应的常量类型:Constant_UTF8_Info
1401 00 04 67 65 74 4100 04指的是UTF8编码的字符串占有的字符数为4,有4个字符,表示的字符数值为getA01对应的常量类型:Constant_UTF8_Info
1501 00 03 28 29 4900 03指的是UTF8编码的字符串占有的字符数为3,有3个字符,表示的字符数值为()I01对应的常量类型:Constant_UTF8_Info
1601 00 04 73 65 74 4100 04指的是UTF8编码的字符串占有的字符数为4,有4个字符,表示的字符数值为setA01对应的常量类型:Constant_UTF8_Info
1701 00 04 28 49 29 5600 04指的是UTF8编码的字符串占有的字符数为4,有4个字符,表示的字符数值为(I)A01对应的常量类型:Constant_UTF8_Info
1801 00 0A 53 6F 75 72 63 65 46 69 6C 6500 0A指的是UTF8编码的字符串占有的字符数为10,有10个字符,表示的字符数值为SourceFile01对应的常量类型:Constant_UTF8_Info
1901 00 0D 4D 79 54 65 73 74 30 31 2E 6A 61 76 6100 0D指的是UTF8编码的字符串占有的字符数为13,有13个字符,表示的字符数值为MyTest01.java01对应的常量类型:Constant_UTF8_Info
200C 00 07 00 08NameAndTypeInfo常量类型由三部分组成,第二项指向全限定名常量项索引,即第7项索引;第三项指向字符串字面量的索引,即第8八项索引0C对应的常量类型:Constant_NameAndTypeField ,具体信息查看结构类型
210C 00 05 00 06NameAndTypeInfo常量类型由三部分组成,第二项指向全限定名常量项索引,即第5项索引;第三项指向字符串字面量的索引,即第6项索引0C对应的常量类型:Constant_NameAndTypeField ,具体信息查看结构类型
2201 00 1D 63 6F 6D 2F 7A 68 75 6E 6f 6r 65 2F 62 79 74 65 63 6F 64 65 2F 4D 79 54 65 73 74 30 31对应的UTF8类型, 63 6F 6d 2f 7a 68 75 6e 6f 6r 65 2f 62 79 74 65 63 6f 64 65 2f 4d 79 54 65 73 74 30 31表示的数值为com/zhunode/bytecode/MyTest0101对应的常量类型:Constant_UTF8_INFO ,具体信息查看结构类型
2301 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74对应的UTF8类型, 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74表示的数值为java/lang/Object01对应的常量类型:Constant_UTF8_INFO ,具体信息查看结构类型

到此为止,常量表的字节码就分析完成了,现在就开始看方法表的字节码。
在这里插入图片描述

字节码Access_Flag访问标志

对常量池有初步了解之后,我们可以来看看访问标志信息了,如下:
在这里插入图片描述
访问标志信息包括该Class文件是类还是接口,是否被定义成public,是否是abastract,如果是类,是否被声明为final。而我们定义的源代码就知道文件是类而且是public的:
类文件的访问标志对应的字节码信息如下:
在这里插入图片描述
对上面的类访问标志有了初步认识就继续往下分析,常量池之后的两个字节表示访问标志,则数两个字节。
在这里插入图片描述

0x0021
对应上面的类访问修饰符可知,并没有找到对应的修饰符,这是由于Java的修饰符是可以互相组合的,这里并没有穷举每一种组合,而是为每一个基本的访问修饰符定义了一个值,那如果一个类即是public,又是final时候,此时就会取这两个修饰符的并集:

0x0011 | 0x0010 = 0x0011
// 所以在二进制字节码中就会记录“0x0011”,同样的,回到之前的"0x0021",可以发现是
0x0001 | 0x0020 = 0x0021

所以0x0021:是0x0020和0x0001的并集,表示ACC_PUBLIC与ACC_SUPER。表示该类是public的并能调用父类的。所以在javap -verbose中能看到:
在这里插入图片描述
紧接着访问标志之后两个字节则是当前类的类名,如下:
在这里插入图片描述
所以往后数两个字节:
在这里插入图片描述
注意:这是一个索引,指向常量池,所以从常量池中进行查找。
在这里插入图片描述
紧接着再往后两个字节则是父类类名:

在这里插入图片描述
在这里插入图片描述
同样也在常量池中去找寻:
在这里插入图片描述
紧接着父类名称之后,则是接口信息了:

在这里插入图片描述
进一步展开如下:
在这里插入图片描述
也就是先数两个字节来看一下接口的数量:
在这里插入图片描述
表示当前类没有实现接口,既然接口数为0,则接口名则不会出现了,如下:
在这里插入图片描述

字段表

好,往下则是字段表信息,如下:
在这里插入图片描述
字段表用于描述类和接口中声明的变量。这里包含了类级别变量以及实例变量,但是不包括方法内部声明的局部变量。
接下来需要了解一下“字段表集合”,这是一个比较复杂的概念,虽说展开字段的信息是这样:
在这里插入图片描述
但是变量表是有自己结构的,就像常量池:
在这里插入图片描述
字段表的结构如下:
在这里插入图片描述
其中access_flags是访问标志符,占两个字符;name_index代表字段的名称索引,占两个字符;descriptor_index代表描述符的索引,占2个字节;这三个信息就可以完整的描述一个字段的信息;attributes_count:属性个数,可有可无;
用结构体形式描述为:

field_info{
	u2 access_flags; // 0002
	u2 name_index; // 0005
	u2 descriptor_index; // 0006
	u2 attributes_count; // 0000
	attribute_info attributes[attributes_count];
}

好,回到二进制文件中来分析一下字段信息,先数2个字节来看一下字段的个数:
在这里插入图片描述
表示类中有一个字段,接下来得要根据字段表来看一下该字段的具体信息,首先数2个字节表示修饰符信息,如下:
在这里插入图片描述
所以往后数两个字节呗:
在这里插入图片描述
那它代表什么修饰符呢,上这张表去查一下:
在这里插入图片描述
呃~~什么鬼,木有是0x0002的修饰符嘛,其实它是ACC_PRIVATE,只是未列在此表中,对应源代码确实是嘛:
在这里插入图片描述
接下来两个字节表示字段名称索引,如下:
在这里插入图片描述
继续数数:
在这里插入图片描述
name_index,索引名,去常量池找:
在这里插入图片描述
在这里插入图片描述
再往后两个字节则是描述符的索引:
在这里插入图片描述
数两个字节:
在这里插入图片描述
继续在常量池上找
在这里插入图片描述
接下来两个字节则是属性数量,如下:
在这里插入图片描述
说明该字段木有属性信息,所以之后的attributes也不可能出现在二进制文件当中了,至此,整个字段信息就分析完了!

方法表结构

这一小节来分析方法表的内容
在这里插入图片描述
接着来看一下方法表相关的信息:
在这里插入图片描述
所以往下找两个字节:
在这里插入图片描述

0x0003表明有三个方法,这是因为有一个为默认构造方法,可以通过Bytecode viewer作为辅助查看:
在这里插入图片描述
接着往下则是方法表相关的信息:
在这里插入图片描述
先来看一下表结构:
在这里插入图片描述
也就是说每个方法所对应的方法表结构如上,所以接下来看一下第一个方法的信息,往下数2个字节则是access_flags,如下:
在这里插入图片描述
这是就需要查看对应的方法访问修饰符了:
在这里插入图片描述
表示是public的方法,接下来四个字节为方法名字和索引和描述符的索引,一起看:

在这里插入图片描述
在这里插入图片描述
其对应的常量池信息如下:
在这里插入图片描述
在这里插入图片描述
也就是第一个方法是构造方法,接着往下则是方法的属性相关的东东,注意:这个属性跟方法的变量是两码事,如下:
在这里插入图片描述
所以往下数两个字节,看一下方法属性的个数:
在这里插入图片描述
说明有一个方法属性,而往后两个字节对是属性名称索引,如下:
在这里插入图片描述
对应常量池:
在这里插入图片描述
Code属性,对于每一个方法都有一个Code属性,如下:

{
  public com.zhunode.jvm.MyTest01();
    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 8: 0
        line 10: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Lcom/zhunode/jvm/MyTest01;

  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 13: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/zhunode/jvm/MyTest01;

  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 17: 0
        line 18: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   Lcom/zhunode/jvm/MyTest01;
            0       6     1     a   I
}
SourceFile: "MyTest01.java"

其实就是描述了整个方法的Code信息,Code attribute的作用是保存该方法的结构,如所对应的字节码:

Code_attribute{
	u2 attribute_name_index;
	u4 attribute_length;
	u2 max_stack;
	u2 max_locals;
	u4 code_length;
	u1 code[code_length];
	u2 exception_table_length;
	{
		u2 start_pc;
		u2 end_pc;
		u2 handler_pc;
		u2 catch_type;
	}exception_table[exception_table_length];
	u2 attribute_count;
	attribute_info attributes[attribute_count];
}

其中attribute_name_index则是属性名的索引,也就是Code,接下来还有一些信息下面来解释一下:

  • attribute_length表示attribute所包含的字节数,不包含attribute_name_index和attribute_length字段。
    在这里插入图片描述
    说的是什么意思呢?比如attribute_length=50个字节,则是指往后的50个字节则为整个Code属性的信息,所以往后先数4个字节咱们来看一看长度:
    在这里插入图片描述
    0x0038转为10进制后的值为56,即属性的长度是56,所以往后56个字节为具体code属性信息,如下:
    在这里插入图片描述
    那咱们来jclasslib中来确认一下是否也是显示的56个字节,如下:

在这里插入图片描述

  • max_stack表示这个方法运行的任何时刻所能达到的操作数栈的最大深度。

在这里插入图片描述
也就是在上面56个字节从头数2个字节,则是这个操作数栈的最大深度,咱们来看一下:
在这里插入图片描述
也就是最大的深度为2。如javap -verbose所示:

在这里插入图片描述

  • max_locals表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量。
    在这里插入图片描述
    再继续读2个字节:
    在这里插入图片描述
    也就是局部变量的数目为1,如javap -verbose所示:
    在这里插入图片描述

  • code_length表示该方法所包含的字节码的字节数以及具体的指令码。具体指令码既是该方法被调用时,虚拟机所执行的字节码。
    在这里插入图片描述
    在这里插入图片描述
    也就是占10个字节,所以往后数10个字节:
    在这里插入图片描述
    其实这10个字节是对应在jclasslib中的代码助记符的信息,如下:
    在这里插入图片描述
    所以问题就来了,aload_0不是助记符信息么,怎么就能够跟字节码文件中的字节对应上呢?所谓助记符其实也就是帮忙我们去记忆的符合,在底层其实也是对应的一个个十六进制的数字的,其它aload_0对应的就是2A这个十六进制数字,凭什么这么说?因为有jclasslib这么好的工具能帮我们对应上,将鼠标放在助记符上发现是一个可以点的链接,点一下“aload_0”发现居然链到了oracle的官网上的说明上去了,如下:
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

所以第一个字节已经分析完了,确实是跟助记符对应上了,接着来分析第二个字节:
在这里插入图片描述
而在jsclasslib中对应的第二个助记符是“invokespecial”,点击链到官网看一下:

在这里插入图片描述
而它的作用可以理解成就是调用父类的方法,很明显对于咱们自定义的子类肯定会去调用父类的构造方法,而这个助记符是有参数的:
在这里插入图片描述
其实也就是往后的两个字节就是该助记符所对应的参数,如下:
在这里插入图片描述
对应常量池为:

在这里插入图片描述
也就是构造方法嘛,如jsclasslib所示:

在这里插入图片描述
接下来继续往下走一个字节:
在这里插入图片描述
对应的code结构中的aload_0,如下:
在这里插入图片描述
接下来再往后看一个字节:
在这里插入图片描述
对应的就是Code结构中的下一个属性:
在这里插入图片描述
为啥如此任性呢?因为点击查看以下说明就晓了:
在这里插入图片描述
为啥要push一个1呢?实际就是给咋们定义的成员变量a赋值,如下:
在这里插入图片描述
可见,该变量的赋值是再默认构造函数中进行的,而不是直接进行赋值的,这也就是分析字节码文件的好处,可以更加真实的发现底层细节。
接下来再数一个字节:
在这里插入图片描述
它对应的方法中Code结构的注记符是:putfield。
在这里插入图片描述
点击看一下官网说明:
在这里插入图片描述
接下来的注记符是带有参数的,所以再往后数两个字节:
在这里插入图片描述
对应的常量池:
在这里插入图片描述
也就说给MyTest01的成员变量a赋值为1。
接下来再数一个字节:
在这里插入图片描述
对应的注记符如下:
在这里插入图片描述
点击链接:

在这里插入图片描述
至此整个构造方法就执行完成了。发现通过分析字节码也能获得不少新知识嘛,仅通过这个构造函数的执行流程就能知道了对于我们定义的成员变量原来是再构造方法中进行赋值而非直接赋值的,还是挺有价值的。

好,方法的code分析完成之后,则就得往下进行分析了,先来查看一下结构类型:
在这里插入图片描述
也就是再数两个字节,看一下:
在这里插入图片描述
说明该方法木有异常信息,所以接下来的异常表就不会显示再字节码文件当中了:

在这里插入图片描述
其中关于异常还需要解释一下:

  • exception_table,这里存放的是处理异常的信息。
  • 每个exception_table表项有start_pc、end_pc、handler_pc、catch_type组成。
  • start_pc和end_pc表示再code数组中的start_pc到end_pc处(包含start_pc,不包含end_pc)的指令抛出异常会由这个表项来处理。
  • handler_pc表示处理异常的代码的开始处。catch_type表示会处理异常类型,它指向常量池的异常类。当catch_type为0时,表示处理所有的异常。

接下来就到属性相关的东东了,如下:
在这里插入图片描述
所以往下数2个字节:
在这里插入图片描述
说明该方法有两个属性,往下数两个字节则是第一个属性的名字索引,如下:
在这里插入图片描述
也就是对应第10的常量池,为:
在这里插入图片描述
该属性用来表示code数组中的字节码和Java代码行数之间的关系。这个属性可以用来在调试的时候定位代码执行的行数。而该属性的结构为:
在这里插入图片描述
其中attribute_name_index就是常量索引10,接下来4个字节则时属性的长度attribute_length,如下:
在这里插入图片描述
也就是属性的长度为10,也是就接下来10个字节则为LineNumberTable的属性信息,如下:
在这里插入图片描述

看一下jclasslib:
在这里插入图片描述
下面具体来分析一下这10个字节,根据结构体来看:
在这里插入图片描述
先2个字节表示属性表有几对映射,如下:
在这里插入图片描述
说明有两对映射,然后再回到结构体中,每对映射的内容为:
在这里插入图片描述
每对占4个字节,先看第一对映射:
在这里插入图片描述
也就是start_pc=0;line_number=8,对应于jclasslib:
在这里插入图片描述
由于咱们源代码木有构造方法,所以字节码对应源代码就在第8行,如下:
在这里插入图片描述

接下来看第二对映射:
在这里插入图片描述
也就是start_pc=4,line_number=8,对应于jclasslib:
在这里插入图片描述
因为成员变量的赋值是在构造方法中完成的,所以对应第10行代码:
在这里插入图片描述
好,方法的第一个属性已经完了,接下来以同样的顺序来查看方法的第二个属性信息了,走2个字节来看属性名称索引,如下:
在这里插入图片描述
对应第11个常量池索引,如下:
在这里插入图片描述
它的结构跟LineNumberTable差不多的,往后数4个字节则是局部变量表所占的长度:
在这里插入图片描述

在这里插入图片描述

长度为12,如jclasslib所示:
在这里插入图片描述
然后往后数12个字节则是局部变量的具体信息,首先两个字节则为局部变量的个数:如下:

在这里插入图片描述
额,构造方法哪来的局部变量呢?好奇快,先不管,先来把其他字节分析完成,再往后四个字节表示start_pc和length,如下:
在这里插入图片描述
如jclasslib所示:
在这里插入图片描述
接下来则为局部变量的索引为0,也就是第一个局部变量:
在这里插入图片描述
再往后两个字节则是局部变量对应常量池的索引,如下:

在这里插入图片描述
在这里插入图片描述
再接下来两个字节则是对局部变量的一个描述常量索引,如下:

在这里插入图片描述
所以对应jclasslib中可以看到:
在这里插入图片描述
那思考一下为啥构造方法中会有一个this的局部变量呢?我们知道在所有方法中我们都能使用this关键字来访问当前的对象,而从字节层面来讲其实this是作为方法的第一个参数传进来的,也就是说对于java的一个实例方法而言,最少会有一个this的局部变量存在。

还剩最后两个字节则为stackmaptable信息,JDK1.6加入的,主要做校验检查的,因为0嘛,所以后面肯定木有相关的信息了,这里就直接忽略,如下:
在这里插入图片描述

这里就已经将编译器生成的默认构造方法的字节相关的分析完成了接下来继续分析自定义的方法啦,按照顺序来讲的话,应该是分析getA();
在这里插入图片描述
还是按照方法的结构来分析,先回忆一下方法的结构:

method_info{
	u2 access_flag;
	u2 name_index;
	u2 descriptor_index;
	u2 attribute_count;
	attribute_info attributes[attribute_count];
}

前两个字节表示访问修饰符,所以跟着上次分析的位置数两个字节:
在这里插入图片描述
根据方法表修饰符可知,0x0001的修饰符是ACC_PUBLIC,
接着4个字节分别表示方法名称的索引记忆方法描述符的索引,如下:

在这里插入图片描述
所以数4个字节:
在这里插入图片描述
对应常量池14、15,如下:
在这里插入图片描述
确实如咱们的猜想,第二个方法就是getA(),好,继续往下分析:

在这里插入图片描述
表示方法的属性个数,所以往下数两个字节:
在这里插入图片描述
所以说明只有一个属性,接着再数2个字节则表示属性的名称索引,如下:
在这里插入图片描述
对应常量池:
在这里插入图片描述
如之前所述:每个方法都有一个对应的Code属性, 所以接下来就得来看一下Code属性结构体了,如下:
在这里插入图片描述
接着就是attribute_length了,往下数4个字节
在这里插入图片描述
说明整个Code长度为47个字节,对比一下jclasslib所示:
在这里插入图片描述
继续往下:往下两个字节为:
在这里插入图片描述
所以往下4个字节为:

在这里插入图片描述

  • max_statck表示这个方法运行的任何时刻都能达到的操作数栈的最大深度为1;
  • max_locals表示方法执行期间创建的局部变量的个数为1,而getA()方法压根就没有定义局部变量,根据之前分析的默认构造方法可以得知此局部变量为编译器生成的this变量。

继续往下:
在这里插入图片描述
在这里插入图片描述
长度为5,表示往后5个字节则为对应在jclasslib中代码注记符的信息,也就是方法的执行体,如下:
在这里插入图片描述
第一个字节为:
在这里插入图片描述
刚好对应aload_0,如下:
在这里插入图片描述
在这里插入图片描述
接着第二个字节

在这里插入图片描述
对应注记符为:
在这里插入图片描述
看一下官网对它的解释:

在这里插入图片描述
而且它是有参数的,所以往后两个字节:

在这里插入图片描述
对应常量为:

在这里插入图片描述
所以如jclasslib所看到的:

接下来看最后一个Code的字节,如下:

在这里插入图片描述
对应的注记符为:
在这里插入图片描述
查看官网说明:
在这里插入图片描述
好,接下来则是异常表的信息,如下:
在这里插入图片描述

往后数两个字节:
在这里插入图片描述
因为该方法木有异常,所以为0,那异常信息就可以忽略了,如下:

在这里插入图片描述
接下来则到属性相关的东东了,如下:

在这里插入图片描述
往后数两个字节:

在这里插入图片描述
说明有两个属性,往下数两个字节则是第一个属性的名字索引,如下:

在这里插入图片描述

查看常量池:
在这里插入图片描述

表示的是行号表,所以此时就要看一下行号表的结构体了:
在这里插入图片描述
接下来4个字节则是属性的长度attribute_length,如下:
在这里插入图片描述

所以接下来的6个字符则为该行号表的信息,如下:
在这里插入图片描述

看一下jclasslib:
在这里插入图片描述

下面具体来分析一下这10个字节,根据结构体来看:
在这里插入图片描述
先2个字节表示属性表有几对映射,如下:
在这里插入图片描述
说明有一对映射,然后再回到结构体中,每对映射的内容为:
在这里插入图片描述
每对映射占4个字节,所以:
在这里插入图片描述

也就是start_pc=0;line_number=7,对应于jclasslib:
在这里插入图片描述

对应的源代码为:
在这里插入图片描述

好,方法的第一个属性已经完了,接下来以同样的顺序来查看方法的第二个属性信息了,走2个字节来看属性名称索引,如下:
在这里插入图片描述
对应的常量表:
在这里插入图片描述

它的结构跟LineNumberTable差不多的,往后数四个字节则是局部变量表所占的长度:

在这里插入图片描述
在这里插入图片描述
长度为12,如jclasslib所示:
在这里插入图片描述
然后往后数12个字节则是局部变量的具体信息,如下:
在这里插入图片描述
首先两个字节则为局部变量的个数,如下:
在这里插入图片描述

说明有一个局部变量,再往后四个字节表示start_pc和length,如下:
在这里插入图片描述
如jclasslib所示:
同时,局部变量的索引为0,也就是第一个局部变量,如下:

在这里插入图片描述
再往后两个个字节则是局部变量对应常量池的索引,如下:

在这里插入图片描述
在这里插入图片描述
再接下来两个字节则是对该局部变量的一个描述常量索引,如下:

在这里插入图片描述
在这里插入图片描述
即this的索引为第13行

所以对应jclasslib中可以看到:
在这里插入图片描述
从这个分析又能证明,对于实例方法,都会有一个this局部变量存在的。

还剩最后两个字节则为stackmaptable信息,JDK1.6加入的,主要做校验检查的,因为0嘛所以后面肯定木有相关的信息了,这里就直接忽略,如下:

在这里插入图片描述

至此getA()方法就已经完全分析完了,接着就是第二个方法setA()了:

所以还得来依据方法表来进行分析:

在这里插入图片描述
前两个字节表示访问修饰符:

在这里插入图片描述
根据方法表修饰符可知,0x0001对应地修饰符为:ACC_PUBLIC。

接着4个字节分别表示方法名称地索引以及方法描述符地索引,如下:
在这里插入图片描述

在这里插入图片描述

对应常量池地16,17,如下:
在这里插入图片描述

正如我们所看到的,继续往下分析:

在这里插入图片描述
表示方法的属性个数,所以往下数两个字节:
在这里插入图片描述
所以说明只有一个属性,接着再数2个字节则表示属性的名称索引,如下:
在这里插入图片描述

对应常量池:

在这里插入图片描述

是不是对它异常的亲切了,每个方法必然会有这个属性,所以接下来就得来看一下Code属性结构体了,如下:

在这里插入图片描述

接着则是attribute_length了,往下数四个字节:
在这里插入图片描述
说明整个Code的长度为62个字节,对比一下jclasslib所示:
在这里插入图片描述
继续往下:往下两个字节为:

在这里插入图片描述

所以往后数4个字节:
在这里插入图片描述

max_stack表示这个方法运行的任何时刻所能达到的操作数栈的最大深度为2;max_locals表示方法执行期间创建的局部变量的数目为2,为啥这次变为2个局部变量了呢?因为第一个是隐式的this,第二个则为方法的int参数。

继续往下:
在这里插入图片描述
表示该方法所包含的字节码的字节数以及具体的指令码,所以往后数4个字节:

在这里插入图片描述
长度为6,所以往后再数6个字节则为对应在jclasslib中的代码助记符的信息,也就是方法的执行体,如下:

在这里插入图片描述

第一个字节为:
在这里插入图片描述

刚好对应aload_0,如下:

在这里插入图片描述

在这里插入图片描述

接着第二个字节:
在这里插入图片描述
对应助记符为:
在这里插入图片描述
看一下官网对它的解释:
第三个字节:
在这里插入图片描述

对应的注记符为:
在这里插入图片描述
看一下官网对他的解释:
在这里插入图片描述
而且它是有参数的,所以往后数两个字节:
在这里插入图片描述

对应常量池为:
在这里插入图片描述
所以如jclasslib所看到的:
在这里插入图片描述

接下来最后一个Code的字节,如下:

在这里插入图片描述

对应助记符为:

在这里插入图片描述

至此setA()方法的执行体就已经分析完了。

好,接下来则是异常表的信息,如下:

在这里插入图片描述

往后数2个字节:
在这里插入图片描述
因为该方法木有异常,所以为0,那异常信息就可以忽略了,如下:

在这里插入图片描述

接下来则到属性相关的东东了,如下:

在这里插入图片描述

往后数两个字节:

在这里插入图片描述

说明该方法有两个属性,往下数两个字节则是第一个属性的名字索引,如下:

在这里插入图片描述
也就是对应常量池10,为:
在这里插入图片描述

该属性用来表示code数组中的字节码和Java代码行数之间的关系。这个属性可以用来在调试的时候定位代码执行的行数。而该属性的结构为:

在这里插入图片描述

其中attribute_name_index就是常量索引10,接下来数4个字节则是属性的长度attribute_length,如下:

在这里插入图片描述
也就是属性的长度为10,也就是接下来10个字节则为LineNumberTable的属性信息,如下:

在这里插入图片描述
看一下jclasslib:
在这里插入图片描述

下面具体来分析一下这10个字节,根据结构体来看:

在这里插入图片描述
先2个字节表示属性表有几对映射,如下:
在这里插入图片描述

说明有两对映射,然后再回到结构体中,每对映射的内容为:
在这里插入图片描述
每对占4个字节,先看第一对映射:
在这里插入图片描述

也就是start_pc=0;line_number=17,对应于jclasslib:

在这里插入图片描述

对应源代码:

在这里插入图片描述

接下来看第二对映射:

在这里插入图片描述

也就是start_pc=5;line_number=18,对应于jclasslib:

在这里插入图片描述
对应源代码:

在这里插入图片描述

好,方法的第一个属性已经完了,接下来以同样的顺序来查看方法的第二个属性信息了,走2个字节来看属性名称索引,如下:

在这里插入图片描述
对应第11个常量池索引,如下:
在这里插入图片描述
它的结构跟LineNumberTable差不多的,往后数四个字节则是局部变量表所占的长度:
在这里插入图片描述
在这里插入图片描述

长度为22,如jclasslib所示:

在这里插入图片描述
然后往后数12个字节则是局部变量的具体信息,首先两个字节则为局部变量的个数,如下:

在这里插入图片描述

先分析第一个局部变量,往后四个字节表示start_pc和length,如下:

在这里插入图片描述

如jclasslib所示:

在这里插入图片描述
接下则为局部变量的索引为0,也就是第一个局部变量,如下

在这里插入图片描述
再往后两个字节则是局部变量对应常量池的索引,如下:

在这里插入图片描述
在这里插入图片描述

再接下来两个字节则是对该局部变量的一个描述常量索引,如下:
在这里插入图片描述
在这里插入图片描述

所以对应jclasslib中可以看到:

在这里插入图片描述
还剩最后两个字节则为stackmaptable信息,JDK1.6加入的,主要做校验检查的,因为0嘛所以后面肯定木有相关的信息了,这里就直接忽略,如下:
在这里插入图片描述
再来分析第二个局部变量,往后四个字节表示start_pc和length,如下:
在这里插入图片描述

在这里插入图片描述

接下来则为局部变量的索引为1,也就是第二个局部变量:

在这里插入图片描述

在这里插入图片描述

再接下来两个字节则是对该局部变量的一个描述常量索引,如下:

在这里插入图片描述
在这里插入图片描述
如jclasslib:

在这里插入图片描述

还剩最后两个字节则为stackmaptable信息,JDK1.6加入的,主要做校验检查的,这里就直接忽略,如下:
在这里插入图片描述

至此!!所有类中的方法相关的字节码就全部分析完了,确实够麻烦,最后则是类的属性信息了如下:
在这里插入图片描述
往后两个字节则表明字节码属性的长度:

在这里插入图片描述

有一个文件属性,往后两个字节则为属性名称的常量池索引,如下:

在这里插入图片描述

对应常量池:

在这里插入图片描述

再往后四个字节则为属性所占字节的长度:

在这里插入图片描述
说明attribute_length占2个字节,也就是最后剩的两个字节,如下:
在这里插入图片描述
对应常量:

在这里插入图片描述

如jclasslib所示:
在这里插入图片描述
至此,MyTest01字节码文件就分析完毕,好繁琐呀,好无聊呀。。。

文章参考:https://www.cnblogs.com/webor2006/
书本参考:《Java虚拟机规范 Java SE 8版.pdf》

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值