概述

字节码文件的跨平台性

Java语言:跨平台的语言

当Java源代码成功编译成字节码后,如果想在不同的平台上面运行,则无须再次编译

Java虚拟机:跨语言的平台

Java虚拟机不和包括Java在内的任何语言绑定,它只与Class 文件特定的二进制文件格式所关联。无论使用何种语言进行软件开发,只要能将源文件编译为正确的Class文件,那么这种语言就可以在Java虚拟机上执行。

要让一个Java程序正确地运行在JVM中,Java源码就必须要被编译为符合JVM规范的字节码

  • 前端编译器的主要任务就是负责将符合Java语法规范的Java代码转换为符合JVN规范的字节码文件
  • javac是一种能够将Java源码编译为字节码的前端编译器
  • javac编译器在将Java源码编译为一个有效的字节码文件过程中经历了4个步骤
    1. 词法解析
    2. 语法解析
    3. 语义解析
    4. 生成字节码

Java的前端编译器

Java源代码的编译结果是字节码,那么肯定需要有一种编译器能够将Java源码编译为字节码,承担这个重要责任的就是配置在path环境变量中的javac编译器。javac是一种能够将Java源码编译为字节码的前端编译器。

通过字节码指令查看代码细节

栗子1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class IntegerTest {

public static void main(String[] args) {
Integer x = 5;
Integer y = 5;
System.out.println(x == y); // true

Integer i1 = 10;
Integer i2 = 10;
System.out.println(i1 == i2); // true

Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4); // false
}

}

栗子2

1
2
3
4
5
6
7
8
9
10
public class StringTest {

public static void main(String[] args) {
String str1 = new String("hello") + new String("world");

String str2 = "helloworld";

System.out.println(str1 == str2); // false
}
}

栗子3

变式1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Father {

int x = 10;

public Father() {
this.print();
x = 20;
}

public void print() {
System.out.println("Father.x = " + x);
}
}

public static void main(String[] args) {
// Father.x = 10
Father f = new Father();
}

变式2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Father {

int x = 10;

public Father() {
this.print();
x = 20;
}

public void print() {
System.out.println("Father.x = " + x);
}
}

public static void main(String[] args) {

Father f = new Father();
System.out.println(f.x);
}

Father.x = 10
20

变式3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Father {

int x = 10;

public Father() {
this.print();
x = 20;
}

public void print() {
System.out.println("Father.x = " + x);
}
}

class Son extends Father {

int x = 30;

public Son() {
this.print();
x = 40;
}

public void print() {
System.out.println("Son.x = " + x);
}
}
public class SonTest {

/*
*** outputs
* Son.x = 0
* Son.x = 30
* 20
*/
public static void main(String[] args) {
Father f = new Son();
System.out.println(f.x);
}

}

成员变量(非静态的)赋值过程:

  1. 默认初始化
  2. 显式初始化/代码块中初始化
  3. 构造器中初始化
  4. 有对象后,对象.属性对象.方法方式对成员变量进行赋值

Class 文件

WHAT 字节码文件

源代码经过编译器编译之后便会生成一个字节码文件,字节码是一种二进制的类文件,其内容是JVM的指令,而不像C、C++经由编译器直接生成机器指令

WHAT 字节码指令

Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码(opcode)以及跟随其后的零至多个代表此操作所需参数的操作数(operand)所构成。虚拟机中许多指令并不包含操作数,只有一个操作码。

补充:使用javap指令解析二进制字节码文件

Class 文件结构

Class 类的本质

任何一个Class文件都对应着唯一一个类或接口的定义信息,Class文件实际上它并不一定以磁盘文件的形式存在。Class 文件是一组以8位字节为基础单位的二进制流。

Class 文件格式

Class文件格式采用一种结构:无符号数和表

数据类型 定义 说明
无符号数 无符号数可以用来描述数字、索引引用、数量值或按照utf-8编码构成的字符串值。 其中无符号数属于基本的数据类型。 以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节
表是由多个无符号数或其他表构成的复合数据结构。 所有的表都以“_info”结尾。 由于表没有固定长度,所以通常会在其前面加上个数说明。

Class 文件结构

  • 魔数
  • Class文件版本
  • 常量池
  • 访问标志
  • 类索引,父类索引,接口索引集合
  • 字段表集合
  • 方法表集合
  • 属性表集合

类型 名称 说明 长度 数量
u4 magic 魔数,识别Class文件格式 4个字节 1
u2 minor_version 副版本号(小版本) 2个字节 1
u2 major_version 主版本号(大版本) 2个字节 1
u2 constant_pool_count 常量池计数器 2个字节 1
cp_info constant_pool 常量池表 n个字节 constant_pool_count-1
u2 access_flags 访问标识 2个字节 1
u2 this_class 类索引 2个字节 1
u2 super_class 父类索引 2个字节 1
u2 interfaces_count 接口计数器 2个字节 1
u2 interfaces 接口索引集合 2个字节 interfaces_count
u2 fields_count 字段计数器 2个字节 1
field_info fields 字段表 n个字节 fields_count
u2 methods_count 方法计数器 2个字节 1
method_info methods 方法表 n个字节 methods_count
u2 attributes_count 属性计数器 2个字节 1
attribute_info attributes 属性表 n个字节 attributes_count

magic

  • 每个Class 文件开头的4个字节的无符号整数称为魔数(Magic Number),它是Class文件的标识符。

  • 唯一作用是确定这个文件是否为一个有效合法的Class文件

  • 如果一个Class文件不以0xCAFEBABE开头,虚拟机在进行文件校验时就会直接抛出以下错误

  • 使用魔数而不是扩展名来进行识别主要是基于安全方面,因为文件扩展名可以随意地改动

Class 文件版本号

  • 紧接着魔数的4个字节存储的是Class文件的版本号,第5个和第6个字节所代表的含义就是编译的副版本号minor_version,而第7个和第8个字节就是编译的主版本号major_version
  • 它们共同构成Class文件的格式版本号,如某个 class 文件的主版本号为M,副版本号为 m,那么这个Class 文件的格式版本号就确定为M.m

Class文件版本号和平台的对应

主版本(十进制) 副版本(十进制) 编译器版本
45 3 1.1
46 0 1.2
47 0 1.3
48 0 1.4
49 0 1.5
50 0 1.6
51 0 1.7
52 0 1.8
53 0 1.9
54 0 1.10
55 0 1.11
  • Java 的版本号是从45开始的,JDK 1.1之后的每个DK大版本发布主版本号向上加1
  • 不同版本的Java编译器编译的Class文件对应的版本是不一样的。目前,高版本的Java虚拟机可以执行由低版本编译器生成的Class文件,但是低版本的Java虚拟机不能执行由高版本编译器生成的Class文件。否则JVM会抛出java.lang.UnsupportedClassVersionError异常。
  • 在实际应用中,由于开发环境和生产环境的不同,可能会导致该问题的发生

常量池

在版本号之后,紧跟的是常量池的数量,以及若干常量池表项。常量池中常量的数量不固定,在常量池的入口放置一项u2类型的无符号数,代表常量池容量计数值(constant_pool_count),该容量计数是从1而不是0开始的

Class文件采用了一个前置的容量计数器(constant_pool_count)和若干连续的数据项(constant_pool)的形式来描述常量池内容,将这系列连续常量池数据称为常量池集合。

常量池表项中用于存放编译时期生成的各种字面量和符号引用,该部分内容将在类加载后进入方法区的运行时常量池存放

常量池计数器 constant_pool_count

  • 由于常量池的数量不固定,因此需要放置两个字节来表示常量池容量计数器
  • 常量池容量计数值(u2类型):从1开始表示常量池中常量下项数量,constant_pool_count=1表示有0个常量项

栗子

常量池表

  • constant_pool是一种表结构,以1~constant_pool_count-1为索引

  • 常量池主要存放两大常量:字面量(Literal)和符号引用(Symbolic References)

  • 它包含Class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其他常量。常量池中每一项都具备相同的特征。第1个字节为类型标记,用于确定该项的格式,该字节称为tag byte(标记字节、标签字节)

类型 标志(或标识) 描述
CONSTANT_utf8_info 1 UTF-8编码的字符串
CONSTANT_Integer_info 3 整型字面量
CONSTANT_Float_info 4 浮点型字面量
CONSTANT_Long_info 5 长整型字面量
CONSTANT_Double_info 6 双精度浮点型字面量
CONSTANT_Class_info 7 类或接口的符号引用
CONSTANT_String_info 8 字符串类型字面量
CONSTANT_Fieldref_info 9 字段的符号引用
CONSTANT_Methodref_info 10 类中方法的符号引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
CONSTANT_NameAndType_info 12 字段或方法的符号引用
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MethodType_info 16 标志方法类型
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

描述符

描述符是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示

标志符 含义
B 基本数据类型byte
C 基本数据类型char
D 基本数据类型double
F 基本数据类型float
I 基本数据类型int
J 基本数据类型long
S 基本数据类型short
Z 基本数据类型boolean
V 代表void类型
L 对象类型,比如:Ljava/lang/Object;
[ 数组类型,代表一维数组。比如:double[][][] is [[[D

用描述符来描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号()之内。如方法java.lang.String toString()的描述符为() Ljava/lang/String;,方法int abc(int[] x, int y)的描述符为([II) I

虚拟机在加载Class文件时才会进行动态链接,Class文件中不会保存各个方法和字段的最终内存布局信息,这些字段和方法的符号引用不经过转换是无法直接被虚拟机使用的。当虚拟机运行时,需要从常量池中获得对应i的符号引用,再在类加载过程中的解析阶段将其替换为直接引用,并翻译到具体的内存地址中

  • 符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到了内存中。
  • 直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那说明引用的目标必定己经存在于内存。

常量类型和结构细节

参数 说明
CONSTANT_Class_info 表示类或接口
CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info 表示字段、方法和接口方法
CONSTANT_String_info 表示String类型的常量对象
CONSTANT_Integer_info、CONSTANT_Float_info 表示4字节(int和float)的数值常量
COISTANT_Long_info、CONSTANT_Double_info 表示8子节(long 和double)的数值常量
CONSTANT_NameAndType_info 表示字段或方法,该参数没有指明该字段或方法所属的类或接口
CONSTANT_Utf8_info 表示字符常量的值
CONSTANT_MethodHandle_info 表示方法句柄
CONSTANT_MethodType_info 表示方法类型
CONSTANT_InvokeDynamic_info 表示invokedynamic指令所用到的引导方法、引导方法所用到的动态调用名称、参数和返回类型,并可以给引导方法传入一系列称为静态参数的常量

解析方法

  • javap -verbose Demo.class
  • 使用工具jclasslib

访问标志

访问标志(access_flag)

在常量池后,紧跟着访问标记。该标记使用两个字节表示,用于识别一些类或者接口层次的访问信息:

标志名称 标志值 含义
ACC_PUBLIC 0x0001 标志为public类型
ACC_FINAL 0x0010 标志被声明为final,只有类可以设置
ACC_SUPER 0x0020 标志允许使用invokespecial字节码指令的新语义,JDK1.0.2之后编译出来的类的这个标志默认为真。(使用增强的方法调用父类方法)
ACC_INTERFACE 0x0200 标志这是一个接口
ACC_ABSTRACT 0x0400 是否为abstract类型,对于接口或者抽象类来说,次标志值为真,其他类型为假
ACC_SYNTHETIC 0x1000 标志此类并非由用户代码产生(即:由编译器产生的类,没有源码对应)
ACC_ANNOTATION 0x2000 标志这是一个注解
ACC_ENUM 0x4000 标志这是一个枚举
  • 类的访问权限通常为ACC_开头的常量
  • 每一种类型的表示都是通过设置访问标记的32位中的特定位来实现的。比如,若是public final的类,则该标记为ACC_PUBLIC ACC_FINAL
  • 使用ACC_SUPER可以让类更准确地定位到父类的方法super.method(),现代编译器都会设置并且使用这个标记。

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

在访问标记后,会指定该类的类别、父类类别以及实现的接口:

长度 含义
u2 this_class
u2 super_class
u2 interfaces_count
u2 interfaces[interfaces_count]
  • 类索引用于确定这个类的全限定名
  • 父类索引用于确定这个类的父类的全限定名。由于Java语言不允许多重继承,所以父类索引只有一个,除java.lang.Object外,所有Java类的父类索引都不为0
  • 接口索引集合就用来描述类实现的接口,被实现的接口将按implements语句(如果这个类本身是一个接口,则应当是extends语句)后的接口顺序从左到右排列在接口索引集合中

this_class(类索引)

类索引是2字节无符号整数,指向常量池的索引。提供类的全限定名,如com/cyan/java1/Demothis_class的值必须是对常量池表中某项的一个有效索引值。常量池在这个索引处的成员必须为CONSTANT_Class_info类型结构体,该结构体表示这个class文件所定义的类或接口

super_class(父类索引)

父类索引是2字节无符号整数,指向常量池的索引。提供当前类的父类的全限定名。如果没有继承任何类,其默认继承的是java/lang/Object类。由于Java不支持多继承,所以其父类只有一个,super_class指向的父类不能是final。

interfaces_count(接口计数器)

interfaces_count项的值表示当前类或接口的直接超接口数量

interfaces

interfaces指向常量池索引集合,提供一个无符号引用到所有已实现的接口,由于一个类可以实现多个接口,因此需要以数组形式保存多个接口的索引,表示接口的每个索引也是一个指向常量池的CONSTANT_Class

interfaces[] (接口索引集合)

interfaces[]中每个成员的值必须是对常量池表中某项的有效索引值,它的长度为interfaces_count。每个成员interfaces[i]必须为CONSTANT_Class_info结构,其中0<=i<interfaces_count。在interfaces[]中,各成员所表示的接口顺序和对应的源代码中给定的接口顺序(从左至右)一样,即interfaces[0]对应的是源代码中最左边的接口。

字段表集合

fields

  • 用于描述接口或类中声明的变量。字段(field)包括类级变量以及实例级变量,但是不包括方法内部、代码块内部声明的局部变量
  • 字段名字、字段数据类型都是无法固定的,只能引用常量池中的常量来描述
  • 它指向常量池索引集合,描述了每个字段的完整信息

注意事项

  • 字段表集合中不会列出从父类或者实现的接口中继承而来的字段,但有可能列出原本Java代码之中不存在的字段。譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段
  • Java中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的

fields_count(字段计数器)

  • fields_count值表示当前Class文件fields表的成员个数,用两个字节来表示
  • fields表中每个成员都是一个field_info结构,用于表示该类或接口所声明的所有类字段或者实例字段,不包括方法内部声明的变量,也不包括从父类或父接口继承的那些字段

fields[] (字段表)

  • fields表中的每个成员都必须是一个fields_info结构的数据项,用于表示当前类或接口中某个字段的完整描述
  • 一个字段的信息包括如下信息。各个修饰符都是布尔值,要么有,要么没有。
    • 作用域(public、 private、protected修饰符)
    • 实例变量还是类变量(static修饰符)
    • 可变性(final)
    • 并发可见性(volatile修饰符,是否强制从主内存读写)
    • 可否序列化(transient修饰符)
    • 字段数据类型(基本数据类型、对象、数组)
    • 字段名称

字段表结构

字段表访问标识

字段名索引

根据字段名索引的值,查询常量池中的指定索引项即可。

属性表集合

一个字段还可能拥有一些属性,用于存储更多的额外信息。比如初始化值、一些注释信息等。属性个数存放在attribute_count中,属性具体内容存放在attributes数组中。

方法表集合

methods

  • methods是指向常量池索引集合,完整描述了每个方法签名

  • 在字节码文件中,每个method_info项都对应着一个类或者接口中的方法信息。比如方法的访问修饰符(public,private或protected),方法的返回值类型以及方法的参数信息等

  • 如果这个方法不是抽象的或者不是native的,那么字节码中会体现出来
  • methods表只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法
  • methods表有可能会出现由编译器自动添加的方法,最典型的便是编译器产生的方法信息(类(接口)初始化方法<clinit>()和实例初始化方法<init>()

注意事项

Java中要重载方法,除要与原方法具有相同名称外,还要求必须拥有原方法不同的特征签名(特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,返回值不会包含在特征签名之中),因此Java无法仅依靠返回值的不同来对一个己有方法进行重载。

但在Class文件中,特征签名的范围更大,只要描述符不是完全一致的两个方法就可以共存,即如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同一个Class文件中

尽管Java语法规范并不允许在一个类或者接口中声明多个方法签名相同的方法,字节码文件中却恰恰允许存放多个方法签名相同的方法,唯一的条件就是方法之间的返回值不能相同。

methods_count(方法计数器)

methods_count的值表示当前Class文件methods表的成员个数。使用两个字节来表示。methods表中每个成员都是一个method_info结构。

methods[] (方法表)

  • methods表中的每个成员都必须是method_info结构,用于表示当前类或接口中某个方法的完整描述。如果某个method_info结构的access_flags项既没有设置ACC_NATIVE标志也没有设置ACC_ABSTRACT标志,那么该结构中应包含实现这个方法所用的Java虚拟机指令。
  • method_info结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、实例初始化方法和类或接口初始化方法

方法表访问标志

属性表集合

attributes(属性表集合)

  • 方法表集合之后的属性表集合,它是Class文件所携带的辅助信息,比如该Class文件的源文件的名称以及任何带有RetentionPolicy.CLASS 或者RetentionPolicy.RUNTIME的注解。该类信息通常被用于Java虚拟机的验证和运行,以及Java程序的调试

  • 此外,字段表、方法表都可以有自己的属性表,用于描述某些场景专有的信息。

  • 属性表集合的限制没有那么严格,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,但Java虚拟机运行时会忽略掉它不认识的属性

attributes_count(属性计数器)

attributes_count值表示当前class文件属性表的成员个数。属性表中每一项都是attribute_info结构。

attributes[](属性表)

属性表的每个项的值必须是attribute_info结构。属性表的结构比较灵活,各种不同的属性只要满足以下结构即可:

属性的通用格式

类型 名称 数量 含义
u2 attribute_name_index 1 属性名索引
u4 attribute_length 1 属性长度
u1 info attribute_length 属性表

属性类型

属性表实际上可以有很多类型,上面看到的Code属性只是其中一种,Java8里面定义了23种属性。

下面这些是虚拟机中预定义的属性:

属性名称 使用位置 说明
Code 方法表 Java代码编译成的字节码指令
ConstantValue 字段表 final关键字定义的常量池
Deprecated 类,方法,字段表 被声明为deprecated的方法和字段
Exceptions 方法表 方法抛出的异常
EnclosingMethod 类文件 仅当一个类为局部类或者匿名类是才能拥有这个属性,该属性用于标识这个类所在的外围方法
InnerClass 类文件 内部类列表
LineNumberTable Code属性 Java源码的行号与字节码指令的对应关系
LocalVariableTable Code属性 方法的局部变量描述
StackMapTable Code属性 JDK1.6中新增的属性﹐供新的类型检查检验器检查和处理目标方法的局部变是和操作数有所需要的类是否匹配
Signature 类,方法表,字段表 用于支持泛型情况下的方法签名
SourceFile 类文件 记录源文件名称
SourceDebugExtension 类文件 用于存储额外的调试信息
Synthetic 类,方法表,字段表 标志方法或字段为编译器自动生成的
LocalVariableTypeTable 使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
RuntimevisibleAnnotations 类,方法表,字段表 为动态注解提供支持
RuntimelnvisibleAnnotations 类,方法表,字段表 用于指明哪些注解是运行时不可见的

ConstantValue属性

Deprecated属性

Code属性

Code属性就是存放方法体里面的代码,并非所有方法表都有Code属性,如接口或者抽象方法没有具体的方法体,就不会有code属性

InnerClass属性

现在定义一个表示类或接口的Class格式为c。如果c的常量池中包含某个CONSTANT_Class_info成员,且这个成员所表示的类或接口不属于任何一个包,那么c的ClassFile结构的属性表中就必须含有对应的InnerClasses属性。InnerClasses属性是在DK 1.1中为支持内部类和内部接口而引入的,位于ClassFile结构的属性表。

LineNumberTable属性

  • LineNumberTable属性是可选变长属性,位于Code结构的属性表。
  • LineNumberTable属性是用来描述Java源码行号与字节码行号之间的对应关系,在调试的时候定位代码执行的行数
  • start_pc即字节码行号;line_number即Java源代码行号
  • 在Code属性的属性表中,LineNumberTable属性可以按照任意顺序出现
  • 多个 LineNumberTable属性可以共同表示一个行号在源文件中表示的内容,即 LineNumberTable属性不需要与源文件的行一一对应。

LocalVariableTable属性

LocalVariableTable 是可选变长属性,位于Code属性的属性表中。它被调试器用于确定方法在执行过程中局部变量的信息。在Code属性的属性表中,LocalVariableTable属性可以按照任意顺序出现。Code属性中的每个局部变量最多只能有一个LocalVariableTable属性

参数 说明
start pc + length 表示这个变量在字节码中的生命周期起始和结束的偏移位置
index 表示这个变量在局部变量表中的槽位(槽位可复用)
name 表示变量名称
Descriptor 表示局部变量类型描述

Signature属性

Signature属性是可选的定长属性,位于ClassFile,field_infomethod_info结构的属性表中。Java中任何类、接口、初始化方法或成员的泛型签名如果包含类型变量(Type Variables)或参数化类型(Parameterized Types),则 Signature属性会为它记录泛型签名信息

SourceFile属性

使用javap指令解析Class文件

WHAT javap

javap是jdk自带的反解析工具,作用是根据Class字节码文件反解析出当前类对应的code区(字节码指令)、局部变量表、异常表和代码行偏移量映射表、常量池等信息。

javac -g

解析字节码文件得到的信息中,有些信息(如局部变量表、指令和代码行偏移量映射表、常量池中方法的参数名称等等)需要在使用javac编译成class文件时,指定参数才能输出。

指令 说明
javac -g xx.java 直接javac xx.java就不会在生成对应的局部变量表等信息,如果使用javac -g xx.java就可以生成所有相关信息

javap

1
javap <options> <classes> # classes是要反编译的class文件

总结

  • 通过javap命令查看java类反汇编得到的Class文件版本号、常量池、访问标识、变量表、指令代码行号表等等信息。不显示类索引、父类索引、接口索引集合、<clinit>()<init>()等结构
  • 一个方法的执行通常会涉及几块内存的操作
    • java栈:局部变量表、操作数栈。
    • java堆:通过对象的地址引用去操作
    • 常量池。
    • 其他如帧数据区、方法区的剩余部分等情况

附录

Demo.class

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.atguigu.java1;

public class Demo {
private int num = 1;

public Demo() {
}

public int add() {
this.num += 2;
return this.num;
}
}

javap解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
Classfile /C:/Users/songhk/Desktop/2/JavapTest.class    //字节码文件所属的路径
Last modified 2020-9-7; size 1358 bytes //最后修改时间,字节码文件的大小
MD5 checksum 526b4a845e4d98180438e4c5781b7e88 //MD5散列值
Compiled from "JavapTest.java" //源文件的名称
public class com.atguigu.java1.JavapTest
minor version: 0 //副版本
major version: 52 //主版本
flags: ACC_PUBLIC, ACC_SUPER //访问标识
Constant pool: //常量池
#1 = Methodref #16.#46 // java/lang/Object."<init>":()V
#2 = String #47 // java
#3 = Fieldref #15.#48 // com/atguigu/java1/JavapTest.info:Ljava/lang/String;
#4 = Fieldref #15.#49 // com/atguigu/java1/JavapTest.flag:Z
#5 = Fieldref #15.#50 // com/atguigu/java1/JavapTest.num:I
#6 = Fieldref #15.#51 // com/atguigu/java1/JavapTest.gender:C
#7 = Fieldref #52.#53 // java/lang/System.out:Ljava/io/PrintStream;
#8 = Class #54 // java/lang/StringBuilder
#9 = Methodref #8.#46 // java/lang/StringBuilder."<init>":()V
#10 = Methodref #8.#55 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#11 = Methodref #8.#56 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
#12 = Methodref #8.#57 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#13 = Methodref #58.#59 // java/io/PrintStream.println:(Ljava/lang/String;)V
#14 = String #60 // www.atguigu.com
#15 = Class #61 // com/atguigu/java1/JavapTest
#16 = Class #62 // java/lang/Object
#17 = Utf8 num
#18 = Utf8 I
#19 = Utf8 flag
#20 = Utf8 Z
#21 = Utf8 gender
#22 = Utf8 C
#23 = Utf8 info
#24 = Utf8 Ljava/lang/String;
#25 = Utf8 COUNTS
#26 = Utf8 ConstantValue
#27 = Integer 1
#28 = Utf8 <init>
#29 = Utf8 ()V
#30 = Utf8 Code
#31 = Utf8 LineNumberTable
#32 = Utf8 LocalVariableTable
#33 = Utf8 this
#34 = Utf8 Lcom/atguigu/java1/JavapTest;
#35 = Utf8 (Z)V
#36 = Utf8 methodPrivate
#37 = Utf8 getNum
#38 = Utf8 (I)I
#39 = Utf8 i
#40 = Utf8 showGender
#41 = Utf8 ()C
#42 = Utf8 showInfo
#43 = Utf8 <clinit>
#44 = Utf8 SourceFile
#45 = Utf8 JavapTest.java
#46 = NameAndType #28:#29 // "<init>":()V
#47 = Utf8 java
#48 = NameAndType #23:#24 // info:Ljava/lang/String;
#49 = NameAndType #19:#20 // flag:Z
#50 = NameAndType #17:#18 // num:I
#51 = NameAndType #21:#22 // gender:C
#52 = Class #63 // java/lang/System
#53 = NameAndType #64:#65 // out:Ljava/io/PrintStream;
#54 = Utf8 java/lang/StringBuilder
#55 = NameAndType #66:#67 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#56 = NameAndType #66:#68 // append:(I)Ljava/lang/StringBuilder;
#57 = NameAndType #69:#70 // toString:()Ljava/lang/String;
#58 = Class #71 // java/io/PrintStream
#59 = NameAndType #72:#73 // println:(Ljava/lang/String;)V
#60 = Utf8 www.atguigu.com
#61 = Utf8 com/atguigu/java1/JavapTest
#62 = Utf8 java/lang/Object
#63 = Utf8 java/lang/System
#64 = Utf8 out
#65 = Utf8 Ljava/io/PrintStream;
#66 = Utf8 append
#67 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#68 = Utf8 (I)Ljava/lang/StringBuilder;
#69 = Utf8 toString
#70 = Utf8 ()Ljava/lang/String;
#71 = Utf8 java/io/PrintStream
#72 = Utf8 println
#73 = Utf8 (Ljava/lang/String;)V
#######################################字段表集合的信息################################################
{
private int num; //字段名
descriptor: I //字段描述符:字段的类型
flags: ACC_PRIVATE //字段的访问标识

boolean flag;
descriptor: Z
flags:

protected char gender;
descriptor: C
flags: ACC_PROTECTED

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

public static final int COUNTS;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 1 //常量字段的属性:ConstantValue

#######################################方法表集合的信息################################################
public com.atguigu.java1.JavapTest(); //构造器1的信息
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: ldc #2 // String java
7: putfield #3 // Field info:Ljava/lang/String;
10: return
LineNumberTable:
line 20: 0
line 18: 4
line 22: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/atguigu/java1/JavapTest;

private com.atguigu.java1.JavapTest(boolean); //构造器2的信息
descriptor: (Z)V
flags: ACC_PRIVATE
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #2 // String java
7: putfield #3 // Field info:Ljava/lang/String;
10: aload_0
11: iload_1
12: putfield #4 // Field flag:Z
15: return
LineNumberTable:
line 23: 0
line 18: 4
line 24: 10
line 25: 15
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 this Lcom/atguigu/java1/JavapTest;
0 16 1 flag Z

private void methodPrivate();
descriptor: ()V
flags: ACC_PRIVATE
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 28: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/atguigu/java1/JavapTest;

int getNum(int);
descriptor: (I)I
flags:
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: getfield #5 // Field num:I
4: iload_1
5: iadd
6: ireturn
LineNumberTable:
line 30: 0
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this Lcom/atguigu/java1/JavapTest;
0 7 1 i I

protected char showGender();
descriptor: ()C
flags: ACC_PROTECTED
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #6 // Field gender:C
4: ireturn
LineNumberTable:
line 33: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/atguigu/java1/JavapTest;

public void showInfo();
descriptor: ()V //方法描述符:方法的形参列表 、 返回值类型
flags: ACC_PUBLIC //方法的访问标识
Code: //方法的Code属性
stack=3, locals=2, args_size=1 //stack:操作数栈的最大深度 locals:局部变量表的长度 args_size:方法接收参数的个数
//偏移量 操作码 操作数
0: bipush 10
2: istore_1
3: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
6: new #8 // class java/lang/StringBuilder
9: dup
10: invokespecial #9 // Method java/lang/StringBuilder."<init>":()V
13: aload_0
14: getfield #3 // Field info:Ljava/lang/String;
17: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: iload_1
21: invokevirtual #11 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
24: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: return
//行号表:指名字节码指令的偏移量与java源程序中代码的行号的一一对应关系
LineNumberTable:
line 36: 0
line 37: 3
line 38: 30
//局部变量表:描述内部局部变量的相关信息
LocalVariableTable:
Start Length Slot Name Signature
0 31 0 this Lcom/atguigu/java1/JavapTest;
3 28 1 i I

static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=1, args_size=0
0: ldc #14 // String www.atguigu.com
2: astore_0
3: return
LineNumberTable:
line 15: 0
line 16: 3
LocalVariableTable:
Start Length Slot Name Signature
}
SourceFile: "JavapTest.java" //附加属性:指名当前字节码文件对应的源程序文件名