class文件中,常量池、方法表、属性表,异常表等等相关数据解析!小白就跟我一起对照学【class字节码文件分析】

前言:前段时间读《深入java虚拟机》介绍到class文件的时候,由于理论知识较多,人总感觉疲惫不堪,就泛泛阅读了一下。在工作中使用起来知识点知道,但是总是需要查阅各种资料。今天有时间,继续整理常量池后面的相关知识。

文章目录

  • 源码
  • 编译之后的javap文件
  • 1、访问标志
  • 2、类索引、父类索引、接口索引
  • 字段表集合
    • 1、字段表结构
    • 2、字段表的访问标识
  • 方法表集合
    • 1、方法表结构
    • 2、方法表的访问标识
  • code属性表

源码

public class Sample {
    public String m1;
    public String m2;
    public Object [] arr;

    public static void main(String[] args) {
        Sample sample = new Sample();
        sample.m1="22";
        sample.arr=new Object[12];
        System.out.println(sample.m1);
    }
}

编译之后的javap文件

  Last modified 2023-6-2; size 708 bytes
  MD5 checksum fc8bb4833223a10b68449d42080b1695
  Compiled from "Sample.java"
public class com.company.jvm.Sample
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#29         // java/lang/Object."":()V
   #2 = Class              #30            // com/company/jvm/Sample
   #3 = Methodref          #2.#29         // com/company/jvm/Sample."":()V
   #4 = String             #31            // 22
   #5 = Fieldref           #2.#32         // com/company/jvm/Sample.m1:Ljava/lang/String;
   #6 = Class              #33            // java/lang/Object
   #7 = Fieldref           #2.#34         // com/company/jvm/Sample.arr:[Ljava/lang/Object;
   #8 = Fieldref           #35.#36        // java/lang/System.out:Ljava/io/PrintStream;
   #9 = Methodref          #37.#38        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #10 = Utf8               m1
  #11 = Utf8               Ljava/lang/String;
  #12 = Utf8               m2
  #13 = Utf8               arr
  #14 = Utf8               [Ljava/lang/Object;
  #15 = Utf8               
  #16 = Utf8               ()V
  #17 = Utf8               Code
  #18 = Utf8               LineNumberTable
  #19 = Utf8               LocalVariableTable
  #20 = Utf8               this
  #21 = Utf8               Lcom/company/jvm/Sample;
  #22 = Utf8               main
  #23 = Utf8               ([Ljava/lang/String;)V
  #24 = Utf8               args
  #25 = Utf8               [Ljava/lang/String;
  #26 = Utf8               sample
  #27 = Utf8               SourceFile
  #28 = Utf8               Sample.java
  #29 = NameAndType        #15:#16        // "":()V
  #30 = Utf8               com/company/jvm/Sample
  #31 = Utf8               22
  #32 = NameAndType        #10:#11        // m1:Ljava/lang/String;
  #33 = Utf8               java/lang/Object
  #34 = NameAndType        #13:#14        // arr:[Ljava/lang/Object;
  #35 = Class              #39            // java/lang/System
  #36 = NameAndType        #40:#41        // out:Ljava/io/PrintStream;
  #37 = Class              #42            // java/io/PrintStream
  #38 = NameAndType        #43:#44        // println:(Ljava/lang/String;)V
  #39 = Utf8               java/lang/System
  #40 = Utf8               out
  #41 = Utf8               Ljava/io/PrintStream;
  #42 = Utf8               java/io/PrintStream
  #43 = Utf8               println
  #44 = Utf8               (Ljava/lang/String;)V
{
  public java.lang.String m1;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC

  public java.lang.String m2;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC

  public java.lang.Object[] arr;
    descriptor: [Ljava/lang/Object;
    flags: ACC_PUBLIC

  public com.company.jvm.Sample();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/company/jvm/Sample;

  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 com/company/jvm/Sample
         3: dup
         4: invokespecial #3                  // Method "":()V
         7: astore_1
         8: aload_1
         9: ldc           #4                  // String 22
        11: putfield      #5                  // Field m1:Ljava/lang/String;
        14: aload_1
        15: bipush        12
        17: anewarray     #6                  // class java/lang/Object
        20: putfield      #7                  // Field arr:[Ljava/lang/Object;
        23: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        26: aload_1
        27: getfield      #5                  // Field m1:Ljava/lang/String;
        30: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        33: return
      LineNumberTable:
        line 9: 0
        line 10: 8
        line 11: 14
        line 12: 23
        line 13: 33
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      34     0  args   [Ljava/lang/String;
            8      26     1 sample   Lcom/company/jvm/Sample;
}
SourceFile: "Sample.java"

1、访问标志

class文件中,我们可以通过背或记也好,或者通过查阅对照表。可以将常量池中的数据整理出来。常量池的数据,之后又是什么呢?紧接着的就是访问标志

标志名称 标志值 标志意义
ACC_PUBLIC 0X0001 是否为public类型
ACC_FINAL 0X0010 是否被声明为final,只有类可设置
ACC_SUPER 0x0020 在jdk1.0.2之后编译出来的类的这个标志都为真
ACC_INTERFACE 0x0200 标识是一个接口
ACC_ABSTRACT 0x4000 是否为abstract类型,如为真,其他类型均为假,如INTERFACE
ACC_SYNTHETIC 0x1000 标识这个类并非由用户产生
ACC_ANNOTATION 0x2000 标识这是一个注解
ACC_ENUM 0x4000 标识这是一个枚举
ACC_MODULE 0x8000 标识这是一个模块

这里先上一张二进制的图:
class文件中,常量池、方法表、属性表,异常表等等相关数据解析!小白就跟我一起对照学【class字节码文件分析】_第1张图片

下面是常量池之后的class文件截取的部分:

00 21 

访问标志,占用空间:U2,这里占用了十六进制的4个位,则是0x0021。这里我啰嗦一下,一个字节为8个位,对应到十六机制来说【两个位】代表一个字节。eg:0xF标识二进制的0000 1111,去除高位的0,就是1111。
在表格中我们说过,ACC_SUPER标志在jdk1.0.2之后的版本其值都为真,则是0x0020,说明其访问标志ACC_PUBLIC为真!0x0001|0x0020=0x0021

2、类索引、父类索引、接口索引

访问标志结束之后,就来到了我们所声明的类例,如下伪代码

public class dog extend cat implement animal{

}
00 02 00 06 00 00

__类索引、父类索引、接口索引占用的内存均为u2。

u2 索引 说明
00 02 #2 代表当前类的索引,通过查找为com/company/jvm/Sample
00 08 #8 代表当前父类索引,通过查找为Object
00 00 0 代表当前文件没有接口

字段表集合

通过字面意,就能得知这里将要介绍的是类或接口成员字段

//这里写个伪代码
public final static int AGE=10;

1、字段表结构

类型 名称 数量 说明
u2 filed_count 1 字段数量
u2 access_flag 1 访问标志
u2 name_index 1 名称索引
u2 descriptor_index 1 类型索引
u2 attribute_count 1 属性计数器
u2 attributes attribute_count 属性值集合

2、字段表的访问标识

标志名称 标志值 含义
ACC_PUBLIC 0x0001 字段是否为公开
ACC_PRIVATE 0x0002 字段是否为私有
ACC_PROTECTED 0x0004 字段是否为保护
ACC_STATIC 0x0008 字段是否为静态
ACC_FINAL 0x0010 字段是否为Final
ACC_VOLATILE 0x0040 字段是否在并发时可见
ACC_TRANSIENT 0x0080 字段是否序列化
ACC_SYNTHETIC 0x1000 字段是否由编译器自己决定
ACC_ENUM 0x4000 字段是否为枚举
00 03 00 01 00 0a 00 0b  00 00 00 01 00 0c 00 0b 00 00 00 01 00 0d 00 0e 00 00 
位值 说明
第一个成员字段
00 03 代表字段数量有3个
00 01 代表字段访问标志位public
00 0a 代表名称索引为10 名称为 m1
00 0b 代表descriptor的索引值为11 对象类型为Ljava/lang/String;【分号;全限定名】
00 00 代表没有属性数量
第二个成员字段
00 01 代表字段访问标志位为public
00 0c 代表字段名称索引为12,名称为m2
00 0b 代表类型的索引11,类型为 Ljava/lang/String;同上
00 00 代表没有属性
第三个成员字段
00 01 同上
00 0d 名称索引为13,经查找为arr
00 0e 类型索引为14,经查找为 [Ljava/lang/Object;全限定名,其中“[”代表为数组
00 00 代表没有属性,数量为0

方法表集合

唠嗑时间开始,写到这里花了三个多小时。从排版到书写上面确实有很大的提升。此时的我确实有点疲惫。仔细一想,没啥子疲惫不疲惫的,路虽远,但始终在路上,总会到达终点。突然想到书上说过这样的一句:当你在解决一个问题的时候,你会感到很疲惫,这时候千万别放弃。因为大部分的人就此放弃了,而你还在路上行走。当你解决之后,你又比别人强了不少!

写到这里【字段表集合】之后,这里就会很轻松。这里再啰嗦一下,字段表分为:成员字段数量、字段名索引、字段类型索引、字段属性数量、字段属性集合。

1、方法表结构

类型 名称 数量 说明
method_count 方法数量
u2 access_flag 1 访问标志
u2 name_index 1 名称索引
u2 descriptor_index 1 类型索引
u2 attributes_count 1 属性数量
attributes_count attributes_[attributes_count] 1 属性表

2、方法表的访问标识

标志名称 标志值 含义
ACC_PUBLIC 0x0001 方法是否为公开
ACC_PRIVATE 0x0002 方法是否为私有
ACC_PROTECTED 0x0004 方法是否为保护
ACC_STATIC 0x0008 方法是否为静态
ACC_FINAL 0x0010 方法是否为Final
ACC_SYNCHRONIZED 0x0020 方法是否在并发时可见
ACC_BRIDGE 0x0040 方法是不是由编译器产生的桥接方法
ACC_VARCHAR 0x0080 方法是否接收不可定参数
ACC_NATIVE 0x0100 方法是否为Native
ACC_ABSTRACT 0x0400 方法是否为abstract
ACC_STRICT 0x0800 方法是否为strictfp【修饰在接口和类,对精确率类型较高且跨平台的计算结果要求比较严格的清醒的话,建议使用该strictfp关键词。】
ACC_SYNTHETIC 0x1000 方法是否由编译器自动产生

code属性表

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 max_stack 1
u2 max_locals 1
u4 code_length 1
u1 code code_length
u2 exception_table_length 1
exception_info exception_table exception_table_length
u2 attributes_count 1
attribute_info attributes attributes_count

继续上方法表的字节码:

00 02 00 01 00 0f 00 10 00 01 00 11 00 00 00 2f 00 00 00 01 00 00 00 05 2a b7 00 01 b1 00 00

现在对照上面的说明,现在逐一说明:

对照 位码 说明
methods_count 00 02 存在2个方法
acc_flags 00 01 访问标识 public
name_index 00 0f 方法名索引为15,经查为< init >
descriptor_index 00 10 方法描述索引为16,经常为()v
attributes_count 00 01 属性只有一个
attributes_name_index 00 11 属性名索引为17,经查为code
attributes_length 00 00 00 2f 属性值长度为47
max_stack 00 00 最大栈深为0
max_locals 00 01 需要分配1个变量槽,根据同时生存的最大局部变量数和类型计算
code_length 00 00 00 05 字节码长度
code 2a 查看指令表为aload_0
code b7 同上~,invokespecial,调用超类构造方法,实例初始化方法,私有方法
code 00 同上~,nop,什么都不做
code 01 同上~,acoust_null,将null推送至栈顶
code b1 同上~,return ,从当前方法返回void
exception_table_lenght 00 00 当前没有发现异常信息
attributes_count 00 02 该方法的附加属性共有2个
attribute_name_index 0012 属性名索引为18,经查为 LineNumberTable
attribute_length 00 00 00 06 属性长度为6
line_number_table_length 00 01 字节码行号共1行
star_pc 00 00 从字节码第0行开始。此处说的行数是一种抽象的,指的是相对于方法体的偏移
line_number 00 03java行号为3
attribute_name_index 00 13 属性名称索引为19,经查为 LocalVariableTable
attribute_length 00 00 00 0c 属性长度为12
local_variable_table_length 00 01 局部变量表长度为1
star_pc 00 00 局部变量的生命周期开始的字节码偏移量
length 00 05 往后偏移5个地址的长度,star_pc和length的配合使用就是局部变量在字节码中的作用域范围
name_index 00 14 名字索引为20,经查为 this
descriptor_index 00 15 描述索引为21,经查为Lcom/company/jvm/Sample;
index 00 00 这个局部变量在栈帧的局部变量表中变量槽的为之为0
acc_flag 00 09 public的标志0x0001,static的标志0x0008,0x0001
name_index 00 16 名字为索引22,经查为 main
descriptor_index 00 17 描述的索引为23,经查([Ljava/lang/String;)V
attributes_count 00 01 当前方法的属性长度为1
attribute_name_index 00 11 当前属性名称索引值为17,经查为Code
attribute_length 00 00 00 66 当前属性的长度为102
attribute_name_index 00 02 属性名称索引为2,经查为com/company/jvm/Sample
max_stack 00 02 栈帧最大深度为2
max_local 00 02 最大局部变量槽数为2
code_lenght 00 00 00 22 字节码长度为34
code bb 经查字节码指令,0xbb为new
code 00 Nop,什么事都不做
code 02 同上经查,为将int型-1推送至栈顶
code 59 0x59,经查为dump,赋值栈顶数值并压入栈顶
code b7 0xb7,invokespecial,调用超类构造方法,实例化初始方法,私有方法
code 00 Nop 不做任何事
code 03 0x03,将int的0推送至栈顶
code 0x27e 一直都这个位都是code值
exception_table_lenght 00 00 说明没有任何异常信息
attributes_count 00 02 说明有两条
同上面分析~ 同上面分析~ 同上面分析~

到最后8位为Source

标识 位码 说明
attribute_name_index 00 1b 名称索引为27,经查为SourceFile
attribute_length 00 00 00 02 对应值为2
sourcefile_index 00 1c 对应的文件索引值为28, 经查为Sample.java

至此一个简单的文件就翻译完成了,对照着javap和字节码整理之后,确实有一番收获。但是还处于道可道非常道的过程,仍然需要透彻一些!明天继续干~

你可能感兴趣的:(jvm,java,开发语言)