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

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

源码

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."<init>":()V
   #2 = Class              #30            // com/company/jvm/Sample
   #3 = Methodref          #2.#29         // com/company/jvm/Sample."<init>":()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               <init>
  #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        // "<init>":()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."<init>":()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 "<init>":()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_PUBLIC0X0001是否为public类型
ACC_FINAL0X0010是否被声明为final,只有类可设置
ACC_SUPER0x0020在jdk1.0.2之后编译出来的类的这个标志都为真
ACC_INTERFACE0x0200标识是一个接口
ACC_ABSTRACT0x4000是否为abstract类型,如为真,其他类型均为假,如INTERFACE
ACC_SYNTHETIC0x1000标识这个类并非由用户产生
ACC_ANNOTATION0x2000标识这是一个注解
ACC_ENUM0x4000标识这是一个枚举
ACC_MODULE0x8000标识这是一个模块

这里先上一张二进制的图:
在这里插入图片描述

下面是常量池之后的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 000代表当前文件没有接口

字段表集合

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

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

1、字段表结构

类型名称数量说明
u2filed_count1字段数量
u2access_flag1访问标志
u2name_index1名称索引
u2descriptor_index1类型索引
u2attribute_count1属性计数器
u2attributesattribute_count属性值集合

2、字段表的访问标识

标志名称标志值含义
ACC_PUBLIC0x0001字段是否为公开
ACC_PRIVATE0x0002字段是否为私有
ACC_PROTECTED0x0004字段是否为保护
ACC_STATIC0x0008字段是否为静态
ACC_FINAL0x0010字段是否为Final
ACC_VOLATILE0x0040字段是否在并发时可见
ACC_TRANSIENT0x0080字段是否序列化
ACC_SYNTHETIC0x1000字段是否由编译器自己决定
ACC_ENUM0x4000字段是否为枚举
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方法数量
u2access_flag1访问标志
u2name_index1名称索引
u2descriptor_index1类型索引
u2attributes_count1属性数量
attributes_countattributes_[attributes_count]1属性表

2、方法表的访问标识

标志名称标志值含义
ACC_PUBLIC0x0001方法是否为公开
ACC_PRIVATE0x0002方法是否为私有
ACC_PROTECTED0x0004方法是否为保护
ACC_STATIC0x0008方法是否为静态
ACC_FINAL0x0010方法是否为Final
ACC_SYNCHRONIZED0x0020方法是否在并发时可见
ACC_BRIDGE0x0040方法是不是由编译器产生的桥接方法
ACC_VARCHAR0x0080方法是否接收不可定参数
ACC_NATIVE0x0100方法是否为Native
ACC_ABSTRACT0x0400方法是否为abstract
ACC_STRICT0x0800方法是否为strictfp【修饰在接口和类,对精确率类型较高且跨平台的计算结果要求比较严格的清醒的话,建议使用该strictfp关键词。】
ACC_SYNTHETIC0x1000方法是否由编译器自动产生

code属性表

类型名称数量
u2attribute_name_index1
u4attribute_length1
u2max_stack1
u2max_locals1
u4code_length1
u1codecode_length
u2exception_table_length1
exception_infoexception_tableexception_table_length
u2attributes_count1
attribute_infoattributesattributes_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_count00 02存在2个方法
acc_flags00 01访问标识 public
name_index00 0f方法名索引为15,经查为< init >
descriptor_index00 10方法描述索引为16,经常为()v
attributes_count00 01属性只有一个
attributes_name_index00 11属性名索引为17,经查为code
attributes_length00 00 00 2f属性值长度为47
max_stack00 00最大栈深为0
max_locals00 01需要分配1个变量槽,根据同时生存的最大局部变量数和类型计算
code_length00 00 00 05字节码长度
code2a查看指令表为aload_0
codeb7同上~,invokespecial,调用超类构造方法,实例初始化方法,私有方法
code00同上~,nop,什么都不做
code01同上~,acoust_null,将null推送至栈顶
codeb1同上~,return ,从当前方法返回void
exception_table_lenght00 00当前没有发现异常信息
attributes_count00 02该方法的附加属性共有2个
attribute_name_index0012属性名索引为18,经查为 LineNumberTable
attribute_length00 00 00 06属性长度为6
line_number_table_length00 01字节码行号共1行
star_pc00 00从字节码第0行开始。此处说的行数是一种抽象的,指的是相对于方法体的偏移
line_number00 03java行号为3
attribute_name_index00 13属性名称索引为19,经查为 LocalVariableTable
attribute_length00 00 00 0c属性长度为12
local_variable_table_length00 01局部变量表长度为1
star_pc00 00局部变量的生命周期开始的字节码偏移量
length00 05往后偏移5个地址的长度,star_pc和length的配合使用就是局部变量在字节码中的作用域范围
name_index00 14名字索引为20,经查为 this
descriptor_index00 15描述索引为21,经查为Lcom/company/jvm/Sample;
index00 00这个局部变量在栈帧的局部变量表中变量槽的为之为0
acc_flag00 09public的标志0x0001,static的标志0x0008,0x0001
name_index00 16名字为索引22,经查为 main
descriptor_index00 17描述的索引为23,经查([Ljava/lang/String;)V
attributes_count00 01当前方法的属性长度为1
attribute_name_index00 11当前属性名称索引值为17,经查为Code
attribute_length00 00 00 66当前属性的长度为102
attribute_name_index00 02属性名称索引为2,经查为com/company/jvm/Sample
max_stack00 02栈帧最大深度为2
max_local00 02最大局部变量槽数为2
code_lenght00 00 00 22字节码长度为34
codebb经查字节码指令,0xbb为new
code00Nop,什么事都不做
code02同上经查,为将int型-1推送至栈顶
code590x59,经查为dump,赋值栈顶数值并压入栈顶
codeb70xb7,invokespecial,调用超类构造方法,实例化初始方法,私有方法
code00Nop 不做任何事
code030x03,将int的0推送至栈顶
code0x27e一直都这个位都是code值
exception_table_lenght00 00说明没有任何异常信息
attributes_count00 02说明有两条
同上面分析~同上面分析~同上面分析~

到最后8位为Source

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值