Java 字节码

一、字节码

​ 在 Java 中, JVM 可以理解的代码就叫做 字节码(即拓展名为 .class 的文件)。对于 Java 代码,计算机是不能直接运行的,必须要先运行 java 虚拟机,再由 java 虚拟机运行编译后的 java 代码,这个编译后的 java 代码,就是 java 字节码。

为什么 jvm 不能直接运行 java 代码呢?

​ 这是因为在 cpu 层面看来计算机中所有的操作都是一个个指令的运行汇集而成的,java 是高级语言,只有人类才能理解其逻辑,计算机是无法识别的,所以 java 代码必须要先编译成字节码文件,jvm 才能正确识别代码转换后的指令并将其运行。

​ 它不面向任何特定的处理器,只面向虚拟机。Java 代码间接翻译成字节码,存储字节码的文件再交由运行于不同平台的 JVM 虚拟机去读取执行,从而实现一次编写,到处运行的目的。

​ JVM 也不再只支持 java,由此衍生出了许多基于 JVM 的编程语言,如 Groovy,Scala,Koltin 等等。

在这里插入图片描述

二、Java 字节码文件

​ class 文件本质上是一个以 8 位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在 class 文件中,中间无任何分隔符,这使得整个 Class 文件中存储的内容几乎全部都是程序运行的必要数据,没有空隙存在。当遇到需要占用 8 位字节以上空间的数据项时,会按照高位在前的方式分割成若干个 8 位字节进行存储。

​ Java 虚拟机规范规定 Class 文件格式采用一种类似与 C 语言结构体的微结构体来存储数据,这种伪结构体中只有两种数据类型:无符号数和表。

无符号数 属于基本的数据类型,以 u1、u2、u4、u8 来分别代表 1 个字节、2 个字节、4 个字节、8 个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照 UTF-8 编码构成字符串值。

是由多个无符号数或其它表作为数据表构成的符合数据类型,为了与无符号数以及其它结构进行区分,所有表的命名都习惯性的以"_info"结尾。例如,filed_info、method_info、attribute_info 等。

1. Class 文件的结构

​ 整个 Class 文件就是一张表,它由下表中所示的数据项构成。

类型名称数量描述
u4magic1魔数
u2minor_version1次版本号
u2major_version1主版本号
u2constant_pool_count1常量池计数器
cp_infoconstant_poolcontant_pool_count-1常量池
u2access_flags1访问标志
u2this_class1类索引
u2super_class1父类索引
u2interfaces_count1接口计数器
u2interfacesinterfaces_count接口索引集合
u2fields_count1字段表计数器
field_infofieldsfields_count字段表集合
u2methods_count1方法表计数器
method_infomethodsmethods_count方法表集合
u2attributes_count1属性表计数器
attribute_infoattributesattributes_count属性表集合

​ Class 文件中存储的字节严格按照上表中的顺序紧凑的排列在一起。哪个字节代表什么含义,长度是多少,先后顺序如何都是被严格限制的,不允许有任何改变。其中u1、u2、u4、u8代表几个字节的无符号数,在反编译出来的16进制文件中,两个数字代表一个字节,也就是u1。

2. Class 文件结构属性

2.1 魔数与 Class 文件版本

u4 magic; // Class 文件的表标志
u2 minor_version; // Class 的小版本号
u2 major_version; // class 的大版本号

​ 每个 Class 文件的头 4 个字节称为 魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接收的 Class 文件。之所以使用魔数而不是文件后缀名来进行识别主要是基于安全的考虑,因为文件后缀名是可以随意更改的。 Java 规范规定魔数为固定值:0xCAFEBABE。如果读取的文件不是以这个魔数开头,Java 虚拟机将拒绝加载它。

​ 紧接着魔数的四个字节存储的是 Class 文件的版本号:第 5 和第 6 两个字节是次版本号(Mainor Version),第 7 和第 8 个字节是主版本号(Major Version)。高版本的 JDK 能够向下兼容低版本的 Class 文件,虚拟机会拒绝执行超过其版本号的 Class 文件。

JDK 版本是从 45 开始的,JDK 1.0-1.1 使用了 45.0-45.3,所以 JDK1.8 对应的主版本号为 52

由于次版本号在 JDK 12 之前都没有使用过,全为 0

2.2 常量池

u2 constant_pool_count; // 常量池的数量
cp_info constant_pool[constant_pool_count - 1]; // 常量池

​ 主版本号之后是常量池的常量数量,常量池的数量是「constant_pool_count - 1」 。因为常量池中常量的数量是不固定的,所以在常量池入口需要放置一个 u2 类型的数据来表示常量池的容量 「constant_pool_count」,和计算机科学中计数的方法不一样,这个容量是从 1 开始而不是从 0 开始计数。之所以将第 0 项常量空出来是为了满足后面某些指向常量池的索引值的数据在特定情况下需要表达「不引用任何一个常量池项」的含义,这种情况可以把索引值置为 0 来表示。

Class 文件结构中只有常量池的容量计数是从 1 开始的,其它集合类型,包括接口索引集合、字段表集合、方法表集合等容量计数都是从 0 开始。

​ 之后就是常量池的实际内容**「constant_pool」**,常量池可以理解为 Class 文件之中的资源仓库,它是 Class 文件结构中与其它项关联最多的数据类型,也是占用 Class 文件空间最大的数据项之一,同时它还是 Class 文件中第一个出现的表类型数据项。

​ 常量池中主要存放两大类常量: 字面量 和 符号引用。

  • 字面量 比较接近 Java 语言层面的常量概念,如字符串、声明为 final 的常量值等。
  • 符号引用 属于编译原理方面的概念,包含了以下三类常量:
    • 类和接口的全限定名
    • 字段的名称和描述符
    • 方法的名称和描述符

​ 经过 javac 编译后的 Class 文件不会保存方法、字段最终在内存中的布局信息,而是保存其具体地址的符号引用。当虚拟机做类加载时,将会从常量池获得对应的符号引用,再在类创建时或运行时解析到具体的内存地址中。
​ 关于字节码的类型对应如下:

标识字符含义
B基本类型 byte
C基本类型 char
D基本类型 double
F基本类型 float
I基本类型 int
J基本类型 long
S基本类型 short
Z基本类型 boolean
V特殊类型 void
L对象类型,以分号结尾,如 Ljava/lang/Object;
[数组类型,以分号结尾,如 [java/lang/Object;
2.2.1 常量池表项目类型

​ 在 JDK 1.8 中有 14 种常量池项目类型,每一个项目都有特定的表结构,这 14 种表有一个共同的特点:开始的第一位是一个 u1 类型的标志位 -tag 来标识常量的类型,代表当前这个常量属于哪种常量类型。

常量池 tag 类型表

常量类型标志(tag)描述
CONSTANT_Utf8_info1UTF-8 编码的字符串
CONSTANT_Integer_info3整型字面量
CONSTANT_Float_info4浮点型字面量
CONSTANT_Long_info5长整型字面量
CONSTANT_Double_info6双精度浮点型字面量
CONSTANT_Class_info7类或接口的符号引用
CONSTANT_String_info8字符串类型字面量
CONSTANT_Fieldref_info9字段的符号引用
CONSTANT_Methodref10类中方法的符号引用
CONSTANT_InterfaceMethodref_info11接口中方法的符号引用
CONSTANT_NameAndType_info12字段或方法的符号引用
CONSTANT_MethodType_info16标志方法类型
CONSTANT_MethodHandle_info15表示方法句柄
CONSTANT_InvokeDynamic_info18表示一个动态方法调用点
2.2.2 常量池表结构
常量描述项目类型项目描述
CONSTANT_Utf8_infoUTF-8 编码的字符串tagu1值为 1
lengthu2UTF-8 编码的字符串占用的字节数
bytes[length]u1长度为 length 的 UTF-8 编码的字符串
CONSTANT_Integer_info整型字面量tagu1值为 3
bytesu4按照高位在前存储的 int 值
CONSTANT_Float_info浮点型字面量tagu1值为 4
bytesu4按照高位在前存储的 float 值
CONSTANT_Long_info长整型字面量tagu1值为 5
bytesu8按照高位在前存储的 long 值
CONSTANT_Double_info双精度浮点型字面量tagu1值为 6
bytesu8按照高位在前存储的 double 值
CONSTANT_Class_info类或接口的符号引用tagu1值为 7
name_indexu2指向全限定名常量项的索引
CONSTANT_String_info字符串类型字面量tagu1值为 8
string_indexu2指向字符串字面量的索引
CONSTANT_Fieldref_info字段的符号引用tagu1值为 9
class_indexu2指向声明字段的类或接口描述符 CONSTANT_Class_info 的索引项
name_and_type_indexu2指向字段描述符 CONSTANT_NameAndType 的索引项
CONSTANT_Methodref_info类中方法的符号引用tagu1值为 10
class_indexu2指向声明方法的类描述符 CONSTANT_Class_info 的索引项
name_and_type_indexu2指向名称及类型描述符 CONSTANT_NameAndType 的索引项
CONSTANT_InterfaceMethodref_info接口中方法的符号引用tagu1值为 11
class_indexu2指向声明方法的接口描述符 CONSTANT_Class_info 的索引项
name_and_type_indexu2指向名称及类型描述符 CONSTANT_NameAndType 的索引项
CONSTANT_NameAndType_info字段或方法的部分符号引用tagu1值为 12
name_indexu2指向该字段或方法名称常量项的索引
descriptor_indexu2指向该字段或方法描述符常量的索引
CONSTANT_MethodHandle_info表示方法句柄tagu1值为 15
reference_kindu1值必须在 1~9 范围,它决定了方法句柄的类型。方法句柄类型的值表示方法句柄的字节码行为
reference_indexu2值必须是对常量池的有效索引
CONSTANT_MethodType_info标识方法类型tagu1值为 16
descriptor_indexu2值必须是对常量池的有效索引,常量池在该索引处的项必须是 CONSTANT_utf8_info 结构,表示方法的描述符
CONSTANT_InvokeDynamic_info表示一个动态方法调用点tagu1值为 18
boostrap_method_attr_indexu2值必须是对当前 Class 文件中引导方法表的 boostarp_methods[] 数组的有效索引
name_and_type_indexu2值必须是对当前常量池的有效索引,常量池在该索引处的项必须是 CONSTANT_NameAndType_info 结构,表示方法名和方法描述符

​ javap 是 jdk 自带的反解析工具。它的作用就是根据 class 字节码文件,反解析出当前类对应的 code 区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。(javap -v class类名 Or javap -v class类名 -> temp.txt(将结果输出到 temp.txt 文件 ))。

2.3 访问标志

u2 access_flag; // Class 的访问标志

​ 在常量池之后的两个字节代表访问标志(access_flag),这个标志用于识别一些类或者接口层次的访问信息,包括这个 Class 是类还是接口;是否定义为 public 类型;是否定义为 abstract 类型;如果是类的话,是否被声明为 final 等。具体的标志位以及标志的含义见下表:

标志名称标志值含义
ACC_PUBLIC0x0001是否为 public 类型
ACC_FINAL0x0010是否被声明为 final,只有类可设置
ACC_SUPER0x0020是否允许使用 invokespecial 字节码指令的新语言,invokespecial 指令的语意在 JDK 1.0.2 中发生过改变,微聊区别这条指令使用哪种语意,JDK 1.0.2 编译出来的类的这个标志都必须为真
ACC_INTERFACE0x0200标识这是一个接口
ACC_ABSTRACT0x0400是否为 abstract 类型,对于接口或者抽象类来说,此标志值为真,其它类值为假
ACC_SYNTHETIC0x1000标识这个类并非由用户代码维护
ACC_ANNOTATION0x2000标识这是一个注解
ACC_ENUM0x4000标识这是一个枚举

​ access_flags 中一共有 16 个标志位可以使用,当前只定义了其中的 9 个,没有使用到的标志位要求一律为 0。

2.4 类索引、父类索引与接口索引集合

u2 this_class; // 当前类

u2 super_class; // 父类

u2 interfaces_count; // 接口数量

u2 interfaces[interfaces_count]; // 一个类可以实现多个接口

​ Java 类的继承关系由类索引、父类索引和接口索引集合三项确定。类索引(this_class)和父类索引(super_class)都是一个 u2 类型的数据,而接口索引集合(interfaces)是一组 u2 类型的数据集合。

  • 类索引用于确定这个类的全限定名
  • 父类索引用于确定这个类的父类的全限定名
  • 接口索引集合用于描述这个类实现了哪些接口

​ 类索引、父类索引、接口索引都排在访问标志之后。由于所有的类都是 java.lang.Object 类的子类,因此除了 Object 类之外所有类的的父类索引都不为 0。

类索引和父类索引各自指向 CONSTANT_Class_info 的类描述常量,通过 CONSTANT_Class_info 的类型常量中的索引可以找到 CONSTANT_utf8_info 类型的常量中的全限定名字符串,从而获取到该类的全限定名。

2.5 字段表集合

u2 fields_count; // 字段数量

field_info fields[field_count]; // 一个类可以有多个字段

​ 字段表集合(field_info)用于描述接口或者类中声明的变量。字段(field)包括 类变量实例变量,但不包括方法内部声明的局部变量。

字段表的结构

类型名称数量
u2access_flags1
u2name_index1
u2descriptor_index1
u2attributes_count1
attribute_infoattributesattributes_count

​ 字段修饰符放在 access_flags 中可设置的标识符如下,它与类中的访问标志(access_flag)非常相似,都是一个 u2 的数据类型。

标志名称标志值含义
ACC_PUBLIC0x0001字符是否为 public
ACC_PRIVATE0x0002字段是否为 private
ACC_PROTECTED0x0004字段是否为 protected
ACC_STATIC0x0008字段是否为 static
ACC_FINAL0x0010字段是否为 final
ACC_VOLATILE0x0040字段是否为 volatile
ACC_TRANSIENT0x0080字段是否为 transient
ACC_SYNTHETIC0x1000字段是否由编译器自动生成
ACC_ENUM0x4000字段是否为 enum

2.6 方法表集合

u2 methods_count; // 方法数量

method_info methods[methods_count]; // 一个类可以有多个方法

​ Class 文件中对方法的描述和对字段的描述是完全一致的,方法表中的结构表和字段表的结构一样。

​ 因为 volatile 关键字和 transient 关键字不能修饰方法,所以方法表的访问标志中没有 ACC_VOLATILE 和 ACC_TRANSIENT。与之相对的,synchronizes、native、strictfp 和 abstract 关键字可以修饰方法,所以方法表的访问标志中增加了 ACC_SYSNCHRONIZED、ACC_NATIVE、ACC_STICTFP 和 ACC_ABSTRACT 标志。

​ 对于方法里的代码,经过编译器编译成字节码指令后,存放在方法属性表中一个名为 「Code」的属性里面。

方法表的结构

类型名称数量
u2access_flags1
u2name_index1
u2descriptor_index1
u2attributes_count1
attribute_infoattributesattributes_count

​ 方法表的 access_flags 取值:

标志名称标志值含义
ACC_PUBLIC0x0001字符是否为 public
ACC_PRIVATE0x0002字段是否为 private
ACC_PROTECTED0x0004字段是否为 protected
ACC_STATIC0x0008字段是否为 static
ACC_FINAL0x0010字段是否为 final
ACC_SYNCHRONIED0x0020方法是否为 synchronized
ACC_BRIDGE0x0040方法是不是由编译器产生的桥接方法
ACC_VARARGS0x0080方法是否接收不定参数
ACC_NATIVE0x0100方法是否为 native
ACC_ABSTRACT0x0400方法是否为 abstract
ACC_STRICT0x0800方法是否为 strictfp
ACC_SYNTHETIC0x1000方法是否由编译器自动生成

2.7 属性表集合

u2 attributes_count; // 此类的属性表中的属性数

attibute_info attributes[attributes_count]; // 属性表集合

​ 在 Class 文件、字段表、方法表中都可以携带自己的属性表(attribute_info)集合,用于描述某些场景专有的信息。

​ 属性表集合不像 Class 文件中的其它数据项要求这么严格,不强制要求各属性表的顺序,并且只要不与已有属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java 虚拟机在运行时会忽略掉它不认识的属性。在《Java虚拟机规范(JavaSE7)》版本中,预定义属性有21项,此处只展示示例代码所涉及属性表结构。

Code:

​ Java 源文件方法体中的代码经过编译后,最终存储在Code属性内,它的结构如下:

类型名称数量含义
u2arrribute_name_index1指向常量池中某个常量的索引,取值固定为 Code
u4attribute_length1属性值的长度
u2max_stack1操作数栈的最大深度(jvm 运行时会根据这个值来分配栈帧中的操作数栈深度)
u2max_locals1局部变量表所需要的存储空间,单位为 slot
u4code_length1字节码指令长度
u1codecode_length具体的字节码指令(根据 jvm 规范,每个字节码指令占用一个字节,jvm 可以自动识别该指令是否需要接收参数)
u2exception_table_length1异常表个数
exception_infoexception_tableexceprion_table_length具体的异常表
u2attribute_count1属性表个数
attribute_infoattributesattribute_count属性表信息

​ 其中 exception_info 结构如下:

类型名称数量含义
u2start_pc1异常起始行
u2end_pc1异常结束行
u2handler_pc1出现异常,跳转行
u2catch_type1异常类型(当 catch_type 为 0 时,代表任何异常情况都需要转向到 handler_pc 处进行处理)
LineNumberTable :

​ LineNumberTable是Code属性中的一个子属性,用来描述java源文件行号与字节码文件偏移量之间的对应关系。当程序运行抛出异常时,异常堆栈中显示出错的行号就是根据这个对应关系来显示的,它的结构如下:

类型名称含义
u2attribute_name_index1
u4attribute_length1
u2line_number_table_length1
line_number_infoline_number_tableline_number_table_length

​ 其中 line_number_info 结构如下:

类型名称数量含义
u2start_pc1字节码偏移量
u2line_number1java 源文件行号
SourceFile:

​ SourceFile属性用于记录生成这个Class文件的源码文件名称,它的结构如下:

名称类型数量
attribute_name_indexu21
attribute_lengthu41
sourcefile_indexu21

三、示例

​ 通过 Test.java 进行 Class 文件解读学习参考。

代码:

public class Test {
    private int m;

    public int inc(){
        return m + 1;
    }
}

​ 通过以下命令,可以在当前所在路径下生成一个 Test.class 文件。

javac Test.java

二进制信息:

  • 使用 IDEA 插件 BinEd
  • 使用 Sublime 查看
  • vim 查看,输入 :%!xxd 即可查看二进制信息
cafe babe 0000 0034 0013 0a00 0400 0f09
0003 0010 0700 1107 0012 0100 016d 0100
0149 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 0369 6e63
0100 0328 2949 0100 0a53 6f75 7263 6546
696c 6501 0009 5465 7374 2e6a 6176 610c
0007 0008 0c00 0500 0601 0004 5465 7374
0100 106a 6176 612f 6c61 6e67 2f4f 626a
6563 7400 2100 0300 0400 0000 0100 0200
0500 0600 0000 0200 0100 0700 0800 0100
0900 0000 1d00 0100 0100 0000 052a b700
01b1 0000 0001 000a 0000 0006 0001 0000
0001 0001 000b 000c 0001 0009 0000 001f
0002 0001 0000 0007 2ab4 0002 0460 ac00
0000 0100 0a00 0000 0600 0100 0000 0500
0100 0d00 0000 0200 0e
cafe babe:Class 文件魔数。
0000:次版本号:JDK12 之前没有次版本号。
0034:主版本号 JDK8
0013:常量池数为 19

0a:第一个常量标志位 10,代表 Methodref(类中方法的符号引用)
0004:指向第 4 个常量(java/lang/Object),代表 class_index
000f:指向第 15 个常量(<init>:()V),代表 name_and_type_index

09:第二个常量标志位 9,代表字段的符号引用
0003:指向第 3 个常量(Test),代表 class_index
0010:指向第 16 个常量(m:I),代表 name_and_type_index

07:第三个常量标志位 7
0011:指向第 17 个常量(Test),代表 class_index

07:第四个常量标志位 7
0012:指向第 18 个常量(java/lang/Object),代表 class_index

01:第五个常量标志位 1
0001:字符串占用 1 个字节
6d:解码为:m

01:第六个常量标志位 1
0001:字符串占用 1 个字节
49:解码为:I,代表 int 类型

01:第七个常量标志位 1
0006:字符串占用 6 个字节
3c696e69743e:解码为:<init>

01:第八个常量标志位 1
0003:字符串占用 3 个字节
282956:解码为:()V,代表无参数,无返回值

01:第九个常量标志位 1
0004:字符串占用 4 个字节
436f6465:解码为:Code

01:第十个常量标志位 1
000f:字符串占用 15 个字节
4c696e654e756d6265725461626c65:解码为:LineNumberTable

01:第十一个常量标志位 1
0003:字符串占用 3 个字节
696e63:解码为:inc

01:第十二个常量标志位 1
0003:字符串占用 3 个字节
282949:解码为:()I,代表无参数,返回值类型为 int

01:第十三个常量标志位 1
000a:字符串占用 10 个字节
536f7572636546696c65:解码为:SourceFile

01:第十四个常量标志位 1
0009:字符串占用 9 个字节
546573742e6a617661:解码为:Test.java

0c:第十五个常量标志位 12,代表 NameAndType(字段或方法的部分符号引用)
0007:指向第 7 个常量(<init>),代表 name_index
0008:指向第 8 个常量(()V),代表 descriptor_index

0c:第十六个常量标志位 12,代表 NameAndType(字段或方法的部分符号引用)
0005:指向第 5 个常量(m),代表 name_index
0006:指向第 6 个常量(I),代表 descriptor_index

01:第十七个常量标志位 1
0004:字符串占用 4 个字节
54657374:解码为:Test

01:第十八个常量标志位 1 
0010:字符串占用 16 个字节
6a6176612f6c616e672f4f626a656374:解码为:java/lang/Object

0021:该类为 Java 类,被 public 修饰,没有被声明为 final 或 abstract
0003:当前类索引,指向常量池第 3 个常量(Test)
0004:父类索引,指向常量池第 4 个常量(java/lang/Object)
0000:实现 0 个接口
0001:字段数量为 1
0002:第一个字段被 private 修饰
0005:指向常量池第 5 个常量(m),代表 name_index
0006:指向常量池第 6 个常量(I),代表 descriptor_index
0000:属性表计数器为 0,没有额外描述信息
// 第一个字段: 被 private 修饰的常量 m,类型为 int
0002:方法数量为 2 

0001:第一个方法被 public 修饰
0007:指向常量池第 7 个常量(<init>),代表 name_index
0008:指向常量池第 8 个常量(()V),代表 descriptor_index
// 第一个方法: 被 public 修饰的方法 <init>,无参数,无返回值。
0001:属性表计数器为 1,表示此方法的属性集合有 1 项属性
0009:第一个属性表的名称索引指向常量池第 9 个常量(Code)
0000001d:属性值长度为 29
0001:操作数栈深度为 1
0001:局部变量表所需变量槽数为 1
00000005:字节码指令长度为 5
2a:对应指令为 aload_0:将变量槽第一个为 reference 类型的本地变量压入操作数栈栈顶
b7:对应指令为 invokespecial,以栈顶的 reference 类型数据指向的对象作为方法的接收者,调用此对象的实例构造器方法,私有方法,父类方法(接收一个 u2 类型的参数说明具体调用哪个方法)
0001:指向常量池第一个常量,根据常量池得 <init>
b1:对应指令为 return,从当前方法返回void
0000:异常表长度为 0
0001:Code 属性表的子属性表个数为 1
000a:第一个属性表的名称索引指向常量池第 10 个常量(LineNumberTable)
00000006:属性长度为 6
0001:行号表长度为 1
0000:字节码偏移量为 0
0001:java 源文件行号为 1

0001:第二个方法被 public 修饰
000b:指向常量池第 11 个常量(inc),代表 name_index
000c:指向常量池第 12 个常量(()I),代表 descriptor_index
// 第二个方法:被 public 修饰的方法 inc,无参数,返回值类型为 int
0001:属性表计数器为 1,表示此方法的属性集合有 1 项属性
0009:第一个属性表的名称索引指向常量池第 9 个常量(Code)
0000001f:属性值长度为 31
0002:操作数栈深度为 2
0001:局部变量表所需变量槽数为 1
00000007:字节码指令长度为 7
2a:对应指令为 aload_0:将变量槽第一个为 reference 类型的本地变量压入操作数栈栈顶
b4:对应指令为 getfield:获取指定类的实例域,并将其值压入栈顶
00:对应指令为 nop:无操作
02:对应指令为 iconst_m1:将int型-1推送至栈顶
04:对应指令为 iconst_1:将int型1推送至栈顶
60:对应指令为 iadd:将栈顶两int型数值相加并将结果压入栈顶
ac:对应指令为 ireturn:从当前方法返回int
0000:异常表长度为 0
0001:属性表计数器为 1,表示此方法的属性集合有 1 项属性
000a:第一个属性表的名称索引指向常量池第 10 个常量(LineNumberTable)
00000006:属性长度为 6
0001:行号表长度为 1
0000:字节码偏移量为 0
0005:java 源文件行号为 5

0001:属性表计数器为 1,表示此类的属性表集合有 1 项属性
000d:第一个属性表的名称索引指向常量池第 13 个常量(SourceFile)
00000002:属性长度为 2
000e:指向常量池第 14 个常量(Test.java),代表 sourcefile_index

反编译字节码文件:

使用到 java 内置的一个反编译工具 javap 可以反编译字节码文件,用法:javap <options> <classes>

其中 <options> 选项包括:

-help  --help  -?        输出此用法消息
-version                 版本信息
-v  -verbose             输出附加信息
-l                       输出行号和本地变量表
-public                  仅显示公共类和成员
-protected               显示受保护的/公共类和成员
-package                 显示程序包/受保护的/公共类
                        和成员 (默认)
-p  -private             显示所有类和成员
-c                       对代码进行反汇编
-s                       输出内部类型签名
-sysinfo                 显示正在处理的类的
                        系统信息 (路径, 大小, 日期, MD5 散列)
-constants               显示最终常量
-classpath <path>        指定查找用户类文件的位置
-cp <path>               指定查找用户类文件的位置
-bootclasspath <path>    覆盖引导类文件的位置

​ 输入命令 javap -v -p Test.class 查看输出内容:

Classfile /F:/Code/Study/JVM-Study/Test.class
  Last modified 2024-10-3; size 265 bytes
  MD5 checksum 7cab5513115a4982f1d0f04048c0f405
  Compiled from "Test.java"
public class Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#16         // Test.m:I
   #3 = Class              #17            // Test
   #4 = Class              #18            // java/lang/Object
   #5 = Utf8               m
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               inc
  #12 = Utf8               ()I
  #13 = Utf8               SourceFile
  #14 = Utf8               Test.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = NameAndType        #5:#6          // m:I
  #17 = Utf8               Test
  #18 = Utf8               java/lang/Object
{
  public Test();
    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 1: 0

  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 5: 0
}
SourceFile: "Test.java"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值