深入理解Java字节码,刨析修改class文件

151 阅读38分钟

今天一起带大家深入理解Java中class文件内容,以及刨析字节码内容,一起跟着博主来学习吧,内容有点多点击关注,防止本文章丢失喔~~~

首先,先来看看java中jvm的结构图,他们是如何工作的!

Java文件结构

Javap命令解析

javap -c 对类文件进行反编译

javap -p 显示private方法和字段(Javap 会显示访问权限为public、protected、默认级别方法)

javap -v 显示更多详细信息,比如版本号、类访问权限、常量池信息

javap -l 显示行号表和局部变量表

javap -g 生成所有调试信息

java字节码官网地址: https://docs.oracle.com/javase/specs/jvms/se20/html/jvms-4.html#jvms-4.4

Class文件结构

ClassFile{
	u4   magic;  # 魔数
    u2   minor_versoin; # 次版本号
    u2   major_version; # 主版本号
    u2   constant_pool_count; # constant_pool_cout(常量个数)
    cp_info   constant_pool[constant_pool_count-1]; # constant_pool(常量池表)
    u2   access_flags;  # 访问修饰符
    u2   this_class; # This class Name
    u2   super_class; # super class name
    u2   interfaces_count; #Interfaces_count(接口数)
    u2   interfaces[interfaces_count]; #interfaces(接口名称)
    u2   fields_count; #fields_count
    field_info   fields[fields_count];  #fileds(字段表)
    u2   methods_count; #methods_count(方法个数)
    method_info   mathods[methods_count]; #方法表
    u2 attributes_count; #attruibute_count(附加属性个数)
    attributes_info   attributes[attributes_count]; #attrubites(附加属性表)
    
}

魔数

魔数: 文件的开头的 四个字节 是固定 值位 0xCAFEBABE

版本

Java版本与Major Version 关系

JDKmajor version
Java 1.145
Java 1.246
Java 1.347
Java 1.448
Java 5(1.5)49
Java 6(1.6)50
Java 7(1.7)51
Java 852
Java 953
Java 1054
Java 1155

常量池

1、常量池结构

constant_pool_count


struct{
    u2   constant_pool_count; # constant_pool_cout(常量个数)
    cp_info   constant_pool[constant_pool_count-1]; # constant_pool(常量池表)
}

struct cp_info{
    u1 tag;
    u1 info[];
}

1.1、常量池描述:

我们的常量池可以看作我们的java class类的一个资源仓库(比如Java类定的方法和变量信息),我们后面的方法 类的信息的描述信息都是通过索引去常量池中获取。

注:常量池中的第0个位置被我们的jvm占用了表示为null 所以我们通过编译出来的常量池索引是从1开始的。【所指的个数是这个变量constant_pool_count】

1.2、常量池分类:

1.3、常量池细化

📎class常量池类型分类.pdf

类访问标记

1、类访问标记权限

当前类名

1、描述:

this class name 占用二个字节,指向的常量池具体的常量

当前类的父类

1、描述:

this class name 占用二个字节,指向的常量池具体的常量

接口信息

1、描述:

当前类的接口信息(是否实现接口),java中支持最大实现65535个接口,在动态代理中Proxy类中限制了

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

字段表

1、字段表结构

struct {
    u2   fields_count; #fields_count
    field_info   fields[fields_count];  #fileds(字段表)
}

field_info{
	u2    access_flags; #权限修饰符
    u2    name_index; #字段名称索引,指向常量池的字符串常量
    u2    descciptor_index;  #字段描述索引,指向常量池的字符串常量
    u2    attribute_count;  #属性表个数
    attribute_info    attributes[attribute_count];
    
}

2、access_flags字段访问标记

3、descciptor_index字段描述符

在JVM规范中,每个字段或者变量都有描述信息,描述信息的主要作用是 数据类型,方法参数列表,返回值类型等;

其中基本参数类型和void类型都是用一个大写的字符来表示,对象类型是通过一个大写L加全类名表示,这么做的好处就是在保证jvm能读懂class文件的情况下尽量的压缩class文件体积. 如下:

基本数据类型:

B---->byte

C---->char

D---->double

F----->float

I------>int

J------>long

S------>short

Z------>boolean

V------->void

对象类型:

String------>Ljava/lang/String;(后面有一个分号)

对于数组类型: 每一个唯独都是用一个前置 [ 来表示

比如: int[] ------>[ I,

int ------> (I)I

String [][]------>[[Ljava.lang.String;

用描述符来描述方法的,先参数列表,后返回值的格式,参数列表按照严格的顺序放在()中

比如源码 String getUserInfoByIdAndName(int id,String name) 的方法描述符号(

I,Ljava/lang/String;)Ljava/lang/String;

方法表

1、方法表的结构


struct{
    u2    methods_count;
    method_info   methods[methods_count];
}

method_info{
    u2    access_flags; # 权限修饰符
    u2    name_index; # 字段名称索引
    u2    desriptor_index; # 字段描述索引
    u2    attributes_count; # 属性表个数
    attribute_info    attributes[attributes_count];
}

2、attribute_info方法表中的属性表

attribute_info{
    u2  attribute_naem_index; # 指向常量池中的索引
    u4  attribute_length; 
    u1  info[attribute_length];
}

3、code_atrribute属性表结构

Code属性是类文件中最重要的组成部分,包括方法的字节码,每个method都有且只有一个code属性

Code_attribute{
    u2   attribute_name_index; 2个字节属性名称索引
    u4   attribute_length; 4个字节属性所包含的字节数,注:不包括
            					attribute_name_index和attribute_length
    u2   max_stack; 2个字节,最大操作数栈的深度
    u2   max_locals; 2个字节, 最大局部变量表
    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];
}

LineNumberTable{
    u2 attribute_name_index
    u4 attribute_length
    u2 line_number_table_length
    line_number_info [line_number_table]
}

line_number_info{
	u2 字节码对应关系
	u2 start_pc
    u2 源码中的行号
}

LocalVariableTable{
    u2 attribute_name_index
    u4 attribute_length
    u2 local_variable_table_length
    local_variable_info [local_variable_table]
}

local_variable_info{
    u2 start_pc
    u2 length
    u2 name_index
	u2 descriptor_index
	u2 index
}

字节码刨析案例:

为了更加方便理解,在idea中安装插件jclasslib

1、java源代码

package com.yogurt.classParse;

/**
 * @author yogurt
 * @Date 2023/4/22 - 23:03 - 2023
 */
public class AddCode {

    public int add(int a,int b){
        return a+b;
    }

    public static void main(String[] args) {
        int add = new AddCode().add(4, 6);
        System.out.println("add = " + add);
    }
}

2、反编译代码

Classfile /Users/yogurt/idea/class-parse/target/classes/com/yogurt/classParse/AddCode.class
  Last modified 2023-7-22; size 915 bytes
  MD5 checksum de4059f5ad74afcc415ef5b31730abf5
  Compiled from "AddCode.java"
public class com.yogurt.classParse.AddCode
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #13.#32        // java/lang/Object."<init>":()V
   #2 = Class              #33            // com/yogurt/classParse/AddCode
   #3 = Methodref          #2.#32         // com/yogurt/classParse/AddCode."<init>":()V
   #4 = Methodref          #2.#34         // com/yogurt/classParse/AddCode.add:(II)I
   #5 = Fieldref           #35.#36        // java/lang/System.out:Ljava/io/PrintStream;
   #6 = Class              #37            // java/lang/StringBuilder
   #7 = Methodref          #6.#32         // java/lang/StringBuilder."<init>":()V
   #8 = String             #38            // add =
   #9 = Methodref          #6.#39         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #10 = Methodref          #6.#40         // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  #11 = Methodref          #6.#41         // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #12 = Methodref          #42.#43        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #13 = Class              #44            // java/lang/Object
  #14 = Utf8               <init>
  #15 = Utf8               ()V
  #16 = Utf8               Code
  #17 = Utf8               LineNumberTable
  #18 = Utf8               LocalVariableTable
  #19 = Utf8               this
  #20 = Utf8               Lcom/yogurt/classParse/AddCode;
  #21 = Utf8               add
  #22 = Utf8               (II)I
  #23 = Utf8               a
  #24 = Utf8               I
  #25 = Utf8               b
  #26 = Utf8               main
  #27 = Utf8               ([Ljava/lang/String;)V
  #28 = Utf8               args
  #29 = Utf8               [Ljava/lang/String;
  #30 = Utf8               SourceFile
  #31 = Utf8               AddCode.java
  #32 = NameAndType        #14:#15        // "<init>":()V
  #33 = Utf8               com/yogurt/classParse/AddCode
  #34 = NameAndType        #21:#22        // add:(II)I
  #35 = Class              #45            // java/lang/System
  #36 = NameAndType        #46:#47        // out:Ljava/io/PrintStream;
  #37 = Utf8               java/lang/StringBuilder
  #38 = Utf8               add =
  #39 = NameAndType        #48:#49        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #40 = NameAndType        #48:#50        // append:(I)Ljava/lang/StringBuilder;
  #41 = NameAndType        #51:#52        // toString:()Ljava/lang/String;
  #42 = Class              #53            // java/io/PrintStream
  #43 = NameAndType        #54:#55        // println:(Ljava/lang/String;)V
  #44 = Utf8               java/lang/Object
  #45 = Utf8               java/lang/System
  #46 = Utf8               out
  #47 = Utf8               Ljava/io/PrintStream;
  #48 = Utf8               append
  #49 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #50 = Utf8               (I)Ljava/lang/StringBuilder;
  #51 = Utf8               toString
  #52 = Utf8               ()Ljava/lang/String;
  #53 = Utf8               java/io/PrintStream
  #54 = Utf8               println
  #55 = Utf8               (Ljava/lang/String;)V
{
  public com.yogurt.classParse.AddCode();
    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 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/yogurt/classParse/AddCode;

  public int add(int, int);
    descriptor: (II)I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1
         1: iload_2
         2: iadd
         3: ireturn
      LineNumberTable:
        line 10: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       4     0  this   Lcom/yogurt/classParse/AddCode;
            0       4     1     a   I
            0       4     2     b   I

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: new           #2                  // class com/yogurt/classParse/AddCode
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: iconst_4
         8: bipush        6
        10: invokevirtual #4                  // Method add:(II)I
        13: istore_1
        14: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
        17: new           #6                  // class java/lang/StringBuilder
        20: dup
        21: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
        24: ldc           #8                  // String add =
        26: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        29: iload_1
        30: invokevirtual #10                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        33: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        36: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        39: return
      LineNumberTable:
        line 14: 0
        line 15: 14
        line 16: 39
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      40     0  args   [Ljava/lang/String;
           14      26     1   add   I
}
SourceFile: "AddCode.java"

3、class文件内容

4、class文件-魔数

代表class文件

CAFEBABE

现在我把CA FE BA BE修改成CA FE BA FF,如图:

此时在运行时,出现了无法兼容的一个错误: Incompatible magic value 3405691647 in class file com/yogurt/classParse/AddCode

5、class文件-版本号

对应java中jdk的版本, 这里16进制的34转成10进制为52,正好对应前面所说的java8版本

0000000034

此时我将34修改成:99(没有对应的jdk版本)

在运行时出现了报错: 大概的意思意思就是只能识别52.0版本的,就是我们的jdk1.8

Exception in thread "main" java.lang.UnsupportedClassVersionError: com/yogurt/classParse/AddCode has been compiled by a more recent version of the Java Runtime (class file version 153.0), this version of the Java Runtime only recognizes class file versions up to 52.0

6、class文件-常量个数

这里16进制的38转成10进制为56, 前面说过常量是从下标0开始计算的, 我们可以看到反编译出来的是55个,其中加上下标0(jvm默认下标0的为null) ,刚刚好

0038
Constant pool:
   #1 = Methodref          #13.#32        // java/lang/Object."<init>":()V
   #2 = Class              #33            // com/yogurt/classParse/AddCode
   #3 = Methodref          #2.#32         // com/yogurt/classParse/AddCode."<init>":()V
   #4 = Methodref          #2.#34         // com/yogurt/classParse/AddCode.add:(II)I
   #5 = Fieldref           #35.#36        // java/lang/System.out:Ljava/io/PrintStream;
   #6 = Class              #37            // java/lang/StringBuilder
   #7 = Methodref          #6.#32         // java/lang/StringBuilder."<init>":()V
   #8 = String             #38            // add =
   #9 = Methodref          #6.#39         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #10 = Methodref          #6.#40         // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  #11 = Methodref          #6.#41         // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #12 = Methodref          #42.#43        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #13 = Class              #44            // java/lang/Object
  #14 = Utf8               <init>
  #15 = Utf8               ()V
  #16 = Utf8               Code
  #17 = Utf8               LineNumberTable
  #18 = Utf8               LocalVariableTable
  #19 = Utf8               this
  #20 = Utf8               Lcom/yogurt/classParse/AddCode;
  #21 = Utf8               add
  #22 = Utf8               (II)I
  #23 = Utf8               a
  #24 = Utf8               I
  #25 = Utf8               b
  #26 = Utf8               main
  #27 = Utf8               ([Ljava/lang/String;)V
  #28 = Utf8               args
  #29 = Utf8               [Ljava/lang/String;
  #30 = Utf8               SourceFile
  #31 = Utf8               AddCode.java
  #32 = NameAndType        #14:#15        // "<init>":()V
  #33 = Utf8               com/yogurt/classParse/AddCode
  #34 = NameAndType        #21:#22        // add:(II)I
  #35 = Class              #45            // java/lang/System
  #36 = NameAndType        #46:#47        // out:Ljava/io/PrintStream;
  #37 = Utf8               java/lang/StringBuilder
  #38 = Utf8               add =
  #39 = NameAndType        #48:#49        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #40 = NameAndType        #48:#50        // append:(I)Ljava/lang/StringBuilder;
  #41 = NameAndType        #51:#52        // toString:()Ljava/lang/String;
  #42 = Class              #53            // java/io/PrintStream
  #43 = NameAndType        #54:#55        // println:(Ljava/lang/String;)V
  #44 = Utf8               java/lang/Object
  #45 = Utf8               java/lang/System
  #46 = Utf8               out
  #47 = Utf8               Ljava/io/PrintStream;
  #48 = Utf8               append
  #49 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #50 = Utf8               (I)Ljava/lang/StringBuilder;
  #51 = Utf8               toString
  #52 = Utf8               ()Ljava/lang/String;
  #53 = Utf8               java/io/PrintStream
  #54 = Utf8               println
  #55 = Utf8               (Ljava/lang/String;)V

使用插件也可以看出总共是56个常量,如下图:

7、class文件-常量

结合下面这张图分析

1、第1个常量分析

下面这个是第一个常量具体内容

#1 = Methodref          #13.#32        // java/lang/Object."<init>":()V

结合上面的图可以看到Methodref常量的具体结构体为:

具体字节码内容, 其中A刚好对应tag中的10, class_info占两个字节: 00 0D (10进制内容为: 13) 正好对应下标为#13(#13 = Class )的内容, name_and_type_index占用两个字节: 00 20 (10进制内容为: 32) 正好对应下标为#32(#32 = NameAndType )的内容

0A000D0020

2、第2个常量分析

下面是第二个常量的具体内容

#2 = Class              #33            // com/yogurt/classParse/AddCode

结合上面的图可以看到Class常量的具体结构为:

具体字节码内容中, 07正好对应常量中的tag, name_index占用两个字节: 00 21 (10进制内容为: 33) 正好对应下标为#33( #33 = Utf8 )

070021

3、第3个常量分析

下面是第三个常量的具体内容:

 #3 = Methodref          #2.#32         // com/yogurt/classParse/AddCode."<init>":()V

结合上面的图可以看到Methodref常量的具体结构体为:

具体字节码内容, 其中A刚好对应tag中的10, class_info占两个字节: 00 02 (10进制内容为: 2) 正好对应下标为#2(#2 = Class )的内容, name_and_type_index占用两个字节: 00 20 (10进制内容为: 32) 正好对应下标为#32(#32 = NameAndType )的内容

0A00020020

4、第最后个常量分析

这里因为时间和内容过多原因跳过其他50多个常量分析,具体可以按照上面提供的内容和步骤去分析, 直接分析最后一个常量内容:

#55 = Utf8               (Ljava/lang/String;)V

结合上面的图可以看到Utf-8常量的具体结构体为:

具体字节码内容, 其中A刚好对应tag中的1, length占两个字节: 00 15 (10进制内容为: 21) 对应bytes数组的长度为21, bytes占用1个字节具体内容为:

28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56

010015284C6A6176612F
6C616E672F537472696E
673B2956************************

8、class文件-类访问修饰符

结合前面的内容可以看到修饰符占用两个字节:

0021

结合下面的图可以看到:

value值中: 0x0001 + 0x0020 正好等于 21 ,所以当前类的权限修饰符是ACC_PUBLIC、ACC_SUPER

下面是反编译后的内容,正好和我分析的一致

Classfile /Users/yogurt/idea/class-parse/target/classes/com/yogurt/classParse/AddCode.class
Last modified 2023-7-22; size 915 bytes
MD5 checksum de4059f5ad74afcc415ef5b31730abf5
Compiled from "AddCode.java"
public class com.yogurt.classParse.AddCode
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER

9、class文件-当前类名

当前类名占用两个字节, 对应class文件中的:

0002

前面说过当前类的内容指向的是常量池中的索引, 对应常量池#2( #2 = Class #33 // com/yogurt/classParse/AddCode) , 正是我们的类AddCode

10、class文件-当前类的父类

当前类名占用两个字节, 对应class文件中的:

000D

同样也是指向常量池中的内容, D的10进制为13,对应下标为#13(#13 = Class #44 // java/lang/Object)的内容, 是一个Object对象,根据所有类的父类是Object的特性,可以知道这个是没问题的,当前类没有使用extend继承任何类

11、class文件-接口信息

根据前面提供的接口信息可知, 接口内容分为接口数量、接口名称,其中接口数量占用两个字节, 接口名称占用两个字节

u2   interfaces_count; #Interfaces_count(接口数)
u2   interfaces[interfaces_count]; #interfaces(接口名称)

对应class文件字节码内容:

0000

当前类没有实现任何接口所以字节码内容都是空

12、class文件-字段表

根据前面提供的字段表信息可知, 内容分为字段表数量、具体字段内容,其中字段数量占用两个字节, 具体字段内容是一个数组

struct {
    u2   fields_count; #fields_count
    field_info   fields[fields_count];  #fileds(字段表)
}

field_info{
	  u2    access_flags; #权限修饰符
    u2    name_index; #字段名称索引,指向常量池的字符串常量
    u2    descciptor_index;  #字段描述索引,指向常量池的字符串常量
    u2    attribute_count;  #属性表个数
    attribute_info    attributes[attribute_count];
    
}

对应class文件字节码内容:

0000

当前类中没有具体的字段,所以为空

13、class文件-方法表

根据前面提供的方法表信息可知, 内容分为方法表数量、具体方法内容,其中方法数量占用两个字节, 具体方法内容是一个数组

struct{
    u2    methods_count;
    method_info   methods[methods_count];
}

method_info{
    u2    access_flags; # 权限修饰符
    u2    name_index; # 字段名称索引
    u2    desriptor_index; # 字段描述索引
    u2    attributes_count; # 属性表个数
    attribute_info    attributes[attributes_count];
}

attribute_info{
    u2  attribute_naem_index; # 指向常量池中的索引
    u4  attribute_length; 
    u1  info[attribute_length];
}

Code_attribute{
    u2   attribute_name_index; 2个字节属性名称索引
    u4   attribute_length; 4个字节属性所包含的字节数,注:不包括
            					attribute_name_index和attribute_length
    u2   max_stack; 2个字节,最大操作数栈的深度
    u2   max_locals; 2个字节, 最大局部变量表
    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];
}

方法表数量对应字节码内容:

0003

说明当前类存在三个方法,使用插件查看具体的内容如下:

可以看到确实是有三个方法,分别是init 、add、main

1、第一个方法:

第一个方法method_info具体内容对应的字节码:

0001000E000F0001
1.1、修饰符:

修饰符占用两个字节对应具体字节码内容如下:

0001

根据前面的修饰符的内容可以,当前方法是ACC_PUBLIC,使用插件查看具体内容,确实是public类型的:

1.2、方法名称索引

方法名称索引占用两个字节对应具体字节码内容如下:

000E

方法名称索引指向常量池中的#14( #14 = Utf8 )位置,是一个init构造方法

1.3、方法描述索引

方法描述索引占用两个字节对应具体字节码内容如下:

000F

方法描述索引指向常量池中的#15( #15 = Utf8 ()V)位置,是一个无参返回值

1.4、属性表个数

属性表个数占用两个字节对应具体字节码内容如下:

0001

当前方法属性只有一个

1.5、方法属性

下面是方法属性的结构

attribute_info{
    u2  attribute_naem_index; # 指向常量池中的索引
    u4  attribute_length; 
    u1  info[attribute_length];
}

方法属性对应具体字节码内容如下:

00100000002F
1.5.1、方法属性名称

方法属性名称占用二个字节对应具体字节码内容如下:

0010

方法属性名称指向的是常量池中的#16( #16 = Utf8 Code)位置

1.5.2、属性个数

属性个数占用四个字节对应具体字节码内容如下:

0000002F

属性个数中2F十进制为47,对应具体的attribute_info的info内容

1.6、属性表

属性表的结构为:

注意: 从code属性表的第三个开始,也就是max_stack

Code_attribute{
    u2   attribute_name_index; 2个字节属性名称索引
    u4   attribute_length; 4个字节属性所包含的字节数,注:不包括
            					attribute_name_index和attribute_length
    u2   max_stack; 2个字节,最大操作数栈的深度
    u2   max_locals; 2个字节, 最大局部变量表
    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];
}

LineNumberTable{
    u2 attribute_name_index
    u4 attribute_length
    u2 line_number_table_length
    line_number_info [line_number_table]
}

line_number_info{
	u2 字节码对应关系
	u2 start_pc
  u2 源码中的行号
}

LocalVariableTable{
    u2 attribute_name_index
    u4 attribute_length
    u2 local_variable_table_length
    local_variable_info [local_variable_table]
}

local_variable_info{
    u2 start_pc
    u2 length
    u2 name_index
	  u2 descriptor_index
	  u2 index
}

下面是属性表中对应的具体的字节码:

00010001000000052AB7
0001B100000002001100
00000600010000000700
120000000C0001000000
05001300140000************
1.6.1、操作数栈深度

操作数栈深度占用两个字节对应字节码:

0001

代表当前操作数栈的深度为1,

1.6.2、局部变量表个数

局部变量表深度占用两个字节对应字节码:

0001

代表当前局部变量表的个数为1:

1.6.3、字节码以及指令码
    u4   code_length; 该方法的字节码以及指令码
    u1   code[code_length]; 具体指令

字节码以及指令码占用四个字节对应的内容如下:

00000005

代表字节码以及指令码的长度为5,具体的指令对应的字节码内容如下:

2AB70001B1

2A: 对应的是字节码注记符是aload_0,作用是把当前调用的方法的栈帧中的局部变量表中索引下标为0的值,加载到操作数栈中

B7: 表示的是invokspecial调用父类中的方法,其中00 01 代表指向常量池中的第1个位置

B1: 表示对应字节码指令return

1.6.4、异常表
u2   exception_table_length; 异常表个数
    {
        u2   start_pc; 
        u2   end_pc; 抛出的异常由这个表处理
        u2   handler_pc; 表示异常代码的开始处
        u2   catch_type; 表示被处理流程的异常类型
    } exception_table[exception_table_length]; 异常表结构,用于存放异常信息

异常表的个数对应的字节码内容:

0000

可以看到当前方法没有异常表

1.6.5、Code属性
    u2   attribute_count;
    attribute_info   attributes[attribute_count];

code属性的个数所对应的字节码内容:

0002

表示当前属性存在两个

1.6.6、行表
LineNumberTable{
    u2 attribute_name_index
    u4 attribute_length
    u2 line_number_table_length
    line_number_info [line_number_table]
}

属性名称索引: 占用两个字节对应的字节码内容如下

0011

对应10进制为17,指向索引下标#17的位置

属性长度: 占用四个字节对应字节码内容如下

00000006

属性长度为6,查看对应的插件内容也是6

指令码和源码映射关系: 对应的字节码如下

line_number_info{
	u2 字节码对应关系
	u2 start_pc
    u2 源码中的行号
}
0001

这里只有一对属性关系

指令码和源码具体内容: 对应的字节码如下

00000007

1.6.7、本地方法变量表
LocalVariableTable{
    u2 attribute_name_index
    u4 attribute_length
    u2 local_variable_table_length
    local_variable_info [local_variable_table]
}

local_variable_info{
    u2 start_pc
    u2 length
    u2 name_index
	  u2 descriptor_index
	  u2 index
}

本地变量表的名称: 占用两个字节对应的字节码如下:

0012

指向常量池中#18( #18 = Utf8 LocalVariableTable)的位置,对应的插件的内容

本地变量表中属性的长度: 占用四个字节具体字节码内容如下

0000000C

属性长度为12,正好和插件的内容对应上:

本地变量表个数: 占用两个字节对应字节码内容

0001

表示当前本地变量表的长度为1

1.6.8、local_variable_info结构:
local_variable_info{
    u2 start_pc
    u2 length
    u2 name_index
	  u2 descriptor_index
	  u2 index
}

start_pc: 局部变量生命周期开始的偏移量,占用两个字节对应的字节码内容:

0000

可以看到对应的是下标0,查看反编译后的代码和插件内容

length: 作用覆盖的长度,占用两个字节,对应的字节码内容如下

0005

对应的长度为5,查看反编译后的内容和插件内容

name_index: 局部变量名称,占用两个字节对应的字节码内容如下:

0013

表示对应索引#19( #19 = Utf8 this)的位置,查看对应反编译后的内容和插件的内容,代表着当前this

decp_index: 表示局部变量描述符索引,占用两个字节对应的字节码内容如下:

0014

表示指向索引#20( #20 = Utf8 Lcom/yogurt/classParse/AddCode;)的位置,对应反编译后的内容如下

index: 局部变量表在栈帧局部变量表中slot的位置,当这个变量数据类型为64位时(double和long),占用slot的index和index+1两个,对应的字节码内容如下:

0000

到此,第一个方法解析完毕!

2、第二个方法

2.1、修饰符:

修饰符占用两个字节对应具体字节码内容如下:

0001

根据前面的修饰符的内容可以,当前方法是ACC_PUBLIC,使用插件查看具体内容,确实是public类型的:

2.2、方法名称索引

方法名称索引占用两个字节对应具体字节码内容如下:

0015

方法名称索引指向常量池中的#21( #21 = Utf8 add)位置,是一个add方法

2.3、方法描述索引

方法描述索引占用两个字节对应具体字节码内容如下:

0016

方法描述索引指向常量池中的#22( #22 = Utf8 (II)I)位置

2.4、属性表个数

属性表个数占用两个字节对应具体字节码内容如下:

0001

当前方法属性只有一个

2.5、方法属性

下面是方法属性的结构

attribute_info{
    u2  attribute_naem_index; # 指向常量池中的索引
    u4  attribute_length; 
    u1  info[attribute_length];
}

方法属性对应具体字节码内容如下:

001000000042
2.5.1、方法属性名称

方法属性名称占用二个字节对应具体字节码内容如下:

0010

方法属性名称指向的是常量池中的#16( #16 = Utf8 Code)位置

2.5.2、属性个数

属性个数占用四个字节对应具体字节码内容如下:

00000042

属性个数中2F十进制为66,对应具体的attribute_info的info内容

2.6、属性表

属性表的结构为:

注意: 从code属性表的第三个开始,也就是max_stack

Code_attribute{
    u2   attribute_name_index; 2个字节属性名称索引
    u4   attribute_length; 4个字节属性所包含的字节数,注:不包括
            					attribute_name_index和attribute_length
    u2   max_stack; 2个字节,最大操作数栈的深度
    u2   max_locals; 2个字节, 最大局部变量表
    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];
}

LineNumberTable{
    u2 attribute_name_index
    u4 attribute_length
    u2 line_number_table_length
    line_number_info [line_number_table]
}

line_number_info{
	u2 字节码对应关系
	u2 start_pc
  u2 源码中的行号
}

LocalVariableTable{
    u2 attribute_name_index
    u4 attribute_length
    u2 local_variable_table_length
    local_variable_info [local_variable_table]
}

local_variable_info{
    u2 start_pc
    u2 length
    u2 name_index
	  u2 descriptor_index
	  u2 index
}

下面是属性表中对应的具体的字节码,长度为66:

00020003000000041B1C
60AC0000000200110000
000600010000000A0012
00000020000300000004
00130014000000000004
00170018000100000004
001900180002****************
2.6.1、操作数栈深度

操作数栈深度占用两个字节对应字节码:

0002

代表当前操作数栈的深度为2

2.6.2、局部变量表个数

局部变量表深度占用两个字节对应字节码:

0003

代表当前局部变量表的个数为3

2.6.3、字节码以及指令码
    u4   code_length; 该方法的字节码以及指令码
    u1   code[code_length]; 具体指令

字节码以及指令码占用四个字节对应的内容如下:

00000004

代表字节码以及指令码的长度为4,具体的指令对应的字节码内容如下:

1B1C60AC

对应反编译后的内容

1B: 对应标记符iload_1 ,作用是把当前调用方法的栈帧的局部变量表索引下标为1的变量内容加载到操作数栈的栈顶

1C: 对应标记符iload_2 ,作用是把当前调用方法的栈帧的局部变量表索引下标为2的变量内容加载到操作数栈的栈顶

60: 对应标记符iadd ,作用是把当前操作数栈的两个数出栈,然后做int类型相加把结果在压入栈顶

AC: 对应标记符ireturn,作用是直接返回int类型的内容

2.6.4、异常表
u2   exception_table_length; 异常表个数
    {
        u2   start_pc; 
        u2   end_pc; 抛出的异常由这个表处理
        u2   handler_pc; 表示异常代码的开始处
        u2   catch_type; 表示被处理流程的异常类型
    } exception_table[exception_table_length]; 异常表结构,用于存放异常信息

异常表的个数对应的字节码内容:

0000

可以看到当前方法没有异常表

2.6.5、Code属性
    u2   attribute_count;
    attribute_info   attributes[attribute_count];

code属性的个数所对应的字节码内容:

0002

表示当前属性存在两个分别是如下:

2.6.6、行表
LineNumberTable{
    u2 attribute_name_index
    u4 attribute_length
    u2 line_number_table_length
    line_number_info [line_number_table]
}

属性名称索引: 占用两个字节对应的字节码内容如下

0011

对应10进制为17,指向#17的位置

属性长度: 占用四个字节对应字节码内容如下

00000006

属性长度为6,查看对应的插件内容也是6,对应后面6个字节码是指令和源码的映射关系

指令码和源码映射关系: 对应的字节码如下

line_number_info{
	u2 字节码对应关系
	u2 start_pc
    u2 源码中的行号
}
00010000000A

从上面的字节码可知,当前长度为6,其中前2个字节码代表字节码和源码的对应关系

0001

表示只有一对对应关系

接下来解析剩下的字节码

0000000A

其中00 00代表当前起始位置,看反编译的代码和插件的内容可以看到是从0开始

剩下的00 0A代表行号,可知是行号为10的位置

2.6.7、本地方法变量表

占用字节码如下:

LocalVariableTable{
    u2 attribute_name_index
    u4 attribute_length
    u2 local_variable_table_length
    local_variable_info [local_variable_table]
}

local_variable_info{
    u2 start_pc
    u2 length
    u2 name_index
	  u2 descriptor_index
	  u2 index
}
0012000000200003

本地变量表的名称: 占用两个字节对应的字节码如下:

0012

表示指向索引#18的位置,对应反编译后代码和插件内容如下:

本地变量表中属性的长度: 占用四个字节具体字节码内容如下

00000020

表示当前属性长度为32

本地变量表个数: 占用两个字节对应字节码内容

0003

表示当前有三个变量表,对应插件内容,正好是三行

2.6.8、local_variable_info结构:

根据上面的内容可知,有三行局部变量

第一个局部变量表内容:

local_variable_info{
    u2 start_pc
    u2 length
    u2 name_index
	  u2 descriptor_index
	  u2 index
}
00000004001300140000

start_pc: 局部变量生命周期开始的偏移量,占用两个字节对应的字节码内容:

0000

表示当前从下标为0的位置开始

length: 作用覆盖的长度,占用两个字节,对应的字节码内容如下

0004

对应的长度为4,查看反编译后的内容和插件内容

name_index: 局部变量名称,占用两个字节对应的字节码内容如下:

0013

对应的十进制为19,为下标#19(#19 = Utf8 this),查看反编译后的代码和插件内容如下:

decp_index: 表示局部变量描述符索引,占用两个字节对应的字节码内容如下:

0014

对应的十进制的为20,指向下标为#20的索引,查看反编译后的代码和插件内容如下:

index: 局部变量表在栈帧局部变量表中slot的位置,当这个变量数据类型为64位时(double和long),占用slot的index和index+1两个,对应的字节码内容如下:

0000

第二个局部变量表内容:

local_variable_info{
    u2 start_pc
    u2 length
    u2 name_index
	  u2 descriptor_index
	  u2 index
}
00000004001700180001

start_pc: 局部变量生命周期开始的偏移量,占用两个字节对应的字节码内容:

0000

当前从下标为0的开始,对应反编译和插件内容如下:

length: 作用覆盖的长度,占用两个字节,对应的字节码内容如下:

0004

当前长度为4,对应反编译和插件内容如下:

name_index: 局部变量名称,占用两个字节对应的字节码内容如下:

0017

转成十进制为23是对应我们的变量a,对应反编译和插件内容如下:

decp_index: 表示局部变量描述符索引,占用两个字节对应的字节码内容如下:

0018

转成十进制为24,对应反编译和插件内容如下:

index: 局部变量表在栈帧局部变量表中slot的位置,当这个变量数据类型为64位时(double和long),占用slot的index和index+1两个,对应的字节码内容如下:

0001

对应反编译和插件内容如下:

第二个局部变量表内容:

local_variable_info{
    u2 start_pc
    u2 length
    u2 name_index
	  u2 descriptor_index
	  u2 index
}
00000004001900180002

start_pc: 局部变量生命周期开始的偏移量,占用两个字节对应的字节码内容:

0000

当前从下标为0的开始,对应反编译和插件内容如下

length: 作用覆盖的长度,占用两个字节,对应的字节码内容如下;

0004

当前所占长度为4,对应反编译和插件内容如下

name_index: 局部变量名称,占用两个字节对应的字节码内容如下:

0019

转成十进制为25是指向变量b,对应反编译和插件内容如下

decp_index: 表示局部变量描述符索引,占用两个字节对应的字节码内容如下:

0018

转成十进制为24,对应反编译和插件内容如下

index: 局部变量表在栈帧局部变量表中slot的位置,当这个变量数据类型为64位时(double和long),占用slot的index

和index+1两个,对应的字节码内容如下:

0002

当前slot位置为2,对应反编译和插件内容如下

3、第三个方法

3.1、修饰符:

修饰符占用两个字节对应具体字节码内容如下:

0009

根据前面的修饰符的内容可以,当前方法是ACC_PUBLIC、ACC_STATIC,使用插件查看具体内容,确实是public、static类型的:

3.2、方法名称索引

方法名称索引占用两个字节对应具体字节码内容如下:

001A

方法名称索引指向常量池中的#26( #26 = Utf8 main)位置,是一个main方法

3.3、方法描述索引

方法描述索引占用两个字节对应具体字节码内容如下:

001B

方法名称索引指向常量池中的#27( #27 = Utf8 ([Ljava/lang/String;)V)位置,是一个String类型

3.4、属性表个数

属性表个数占用两个字节对应具体字节码内容如下:

0001

可知当前属性表只有一个

3.5、方法属性

下面是方法属性的结构

attribute_info{
    u2  attribute_naem_index; # 指向常量池中的索引
    u4  attribute_length; 
    u1  info[attribute_length];
}

方法属性对应具体字节码内容如下:

001000000064
3.5.1、方法属性名称

方法属性名称占用二个字节对应具体字节码内容如下:

0010

指向索引#16的位置,对应反编译和插件内容如下:

3.5.2、属性个数

属性个数占用四个字节对应具体字节码内容如下:

00000064

属性个数中64十进制为100,对应具体的attribute_info的info内容

3.6、属性表

属性表的结构为:

注意: 从code属性表的第三个开始,也就是max_stack

Code_attribute{
    u2   attribute_name_index; 2个字节属性名称索引
    u4   attribute_length; 4个字节属性所包含的字节数,注:不包括
            					attribute_name_index和attribute_length
    u2   max_stack; 2个字节,最大操作数栈的深度
    u2   max_locals; 2个字节, 最大局部变量表
    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];
}

LineNumberTable{
    u2 attribute_name_index
    u4 attribute_length
    u2 line_number_table_length
    line_number_info [line_number_table]
}

line_number_info{
	u2 字节码对应关系
	u2 start_pc
  u2 源码中的行号
}

LocalVariableTable{
    u2 attribute_name_index
    u4 attribute_length
    u2 local_variable_table_length
    local_variable_info [local_variable_table]
}

local_variable_info{
    u2 start_pc
    u2 length
    u2 name_index
	  u2 descriptor_index
	  u2 index
}

下面是属性表中对应的具体的字节码,长度为100:

00030002 00000028 BB000259 B7000307 1006B600 043CB200 05BB0006 59B70007 1208B600 091BB600 0AB6000B B6000CB1 00000002 00110000 000E0003 0000000E 000E000F 00270010 00120000 00160002 00000028 001C001D 0000000E 001A0015 00180001

0003000200000028BB00
0259B70003071006B600
043CB20005BB000659B7
00071208B600091BB600
0AB6000BB6000CB10000
000200110000000E0003
0000000E000E000F0027
00100012000000160002
00000028001C001D0000
000E001A001500180001
3.6.1、操作数栈深度

操作数栈深度占用两个字节对应字节码:

0003

当前操作数栈的深度为3,可以查看我们的反编译内容:

3.6.2、局部变量表个数

局部变量表深度占用两个字节对应字节码:

0002

当前局部变量表的大小为2,可以查看对应的反编译内容:

3.6.3、字节码以及指令码
    u4   code_length; 该方法的字节码以及指令码
    u1   code[code_length]; 具体指令

字节码以及指令码占用四个字节对应的内容如下:

00000028

表示当前存在40个指令

BB000259B70003071006
B600043CB20005BB0006
59B700071208B600091B
B6000AB6000BB6000CB1

如图:

BB: 代表new指令, 00 02表示当前指令指向常量池中的#2位置

59: 代表dup指令

B7: 代表invokespecial指令,其中00 03表示指向常量池中#3的位置

07: 代表iconst_4指令

10: 代表bipush指令,其中06代表具体的值

B6: 代表invokevirtual指令,其中00 04 代表指向常量池中下标为#4的位置

3C: 代表istore_1指令

B2: 代表getstatic指令,其中00 05 为指向具体的常量池中的下标索引

BB: 代表new指令,其中00 06为指向常量池中具体的索引位置

59: 代表dup指令

B7: 代表invokespecial指令,其中00 07指向常量池中具体的索引位置

12: 代表ldc指令,其中08指向常量池中索引位置为#8的地址

B6: 代表指令invokevirtual,其中00 09表示指向常量池中#9的位置

1B: 代表iload_1指令

B6: 代表invokevirtual指令,其中00 0A表示指向常量池中索引下标为#10的位置

B6: 代表invokevirtual指令,其中00 0B表示指向常量池中下标为#11的位置

B6: 代表invokevirtual指令,其中00 0C表示指向常量池中下标为#12的位置

B1: 代表return指令

3.6.4、异常表
u2   exception_table_length; 异常表个数
    {
        u2   start_pc; 
        u2   end_pc; 抛出的异常由这个表处理
        u2   handler_pc; 表示异常代码的开始处
        u2   catch_type; 表示被处理流程的异常类型
    } exception_table[exception_table_length]; 异常表结构,用于存放异常信息

异常表的个数对应的字节码内容:

0000

当前没有异常表

3.6.5、Code属性
    u2   attribute_count;
    attribute_info   attributes[attribute_count];

code属性的个数所对应的字节码内容:

0002

当前存在两个属性:

3.6.6、行表
LineNumberTable{
    u2 attribute_name_index
    u4 attribute_length
    u2 line_number_table_length
    line_number_info [line_number_table]
}

属性名称索引: 占用两个字节对应的字节码内容如下

0011

当前指向常量池中索引位置为#17的地方

属性长度: 占用四个字节对应字节码内容如下

0000000E

当长度为14,如图所示:

指令码和源码映射关系: 对应的字节码如下

line_number_info{
	u2 字节码对应关系
	u2 start_pc
    u2 源码中的行号
}
00030000000E

前2个字节码代表字节码和源码的对应关系

0003

当前存在3对关系

第一个对应关系:

0000000E

表示当前起始位置是0, 行号为14

第二个对应关系:

000E000F

表示当前起始位置为: 14, 行号为15

第三个对应关系:

00270010

表示当前起始位置为: 39, 行号为16

3.6.7、本地方法变量表

占用字节码如下:

LocalVariableTable{
    u2 attribute_name_index
    u4 attribute_length
    u2 local_variable_table_length
    local_variable_info [local_variable_table]
}

local_variable_info{
    u2 start_pc
    u2 length
    u2 name_index
	  u2 descriptor_index
	  u2 index
}
0012000000160002

本地变量表的名称: 占用两个字节对应的字节码如下:

0012

表示指向常量池中#18的位置,如图:

本地变量表中属性的长度: 占用四个字节具体字节码内容如下

00000016

当前属性长度为22,如图:

本地变量表个数: 占用两个字节对应字节码内容

0002

当前本地变量存在两个,如图:

3.6.8、local_variable_info结构:

根据上面的内容可知,有两行局部变量

第一个局部变量表内容:

local_variable_info{
    u2 start_pc
    u2 length
    u2 name_index
	  u2 descriptor_index
	  u2 index
}
00000028001C001D0000

start_pc: 局部变量生命周期开始的偏移量,占用两个字节对应的字节码内容:

0000

当前从0开始,如图:

length: 作用覆盖的长度,占用两个字节,对应的字节码内容如下

0028

当前长度为40,如图:

name_index: 局部变量名称,占用两个字节对应的字节码内容如下:

001C

表示指向常量池中#28的位置,如图:

decp_index: 表示局部变量描述符索引,占用两个字节对应的字节码内容如下:

001D

示指向常量池中#29的位置,如图:

index: 局部变量表在栈帧局部变量表中slot的位置,当这个变量数据类型为64位时(double和long),占用slot的index和index+1两个,对应的字节码内容如下:

0000

当前指向slot位置0处

第二个局部变量表内容:

local_variable_info{
    u2 start_pc
    u2 length
    u2 name_index
	  u2 descriptor_index
	  u2 index
}
000E001A001500180001

start_pc: 局部变量生命周期开始的偏移量,占用两个字节对应的字节码内容:

000E

表示当前从14开始, 如图:

length: 作用覆盖的长度,占用两个字节,对应的字节码内容如下

001A

表示当前长度为26,如图:

name_index: 局部变量名称,占用两个字节对应的字节码内容如下:

0015

表示当前指向常量池中下标为#21的位置,如图:

decp_index: 表示局部变量描述符索引,占用两个字节对应的字节码内容如下:

0018

表示当前指向常量池中索引下标为#24的位置,如图:

index: 局部变量表在栈帧局部变量表中slot的位置,当这个变量数据类型为64位时(double和long),占用slot的index和index+1两个,对应的字节码内容如下:

0001

表示当前slot位置为1,如图:

14、class文件-SourceFile

attribute_count(class文件的属性) // 只存在一个属性
{ 
    "attribute_name_index":"u2 指向常量池中SourceFile,属性名", 
    "attribute_length":"u4 表示属性的长度", 
    "sourceFile_index":"u2 表示源文件的索引
}

0001001E 00000002 001F

0001001E00000002001F

上面字节码内容为SourceFile

1、class文件属性:

占用两个字节,下面为具体的字节码内容:

0001

表示当前存在一个属性

2、属性名SourceFile

用两个字节,下面为具体的字节码内容:

001E

表示指向常量池中下标为#30的位置,如图:

3、属性长度

占用两个字节,下面为具体的字节码内容:

00000002

表示当前长度为2,如图:

4、属性文件

占用两个字节,下面为具体的字节码内容:

001F

表示指向常量池中下标为#31的位置,如图: