JVM -- 字节码class文件处理(五)

10 篇文章 2 订阅

阅读前可先参考

JVM -- JVM内存结构:程序计数器、虚拟机栈、本地方法栈、堆、方法区(二)

​​​​​​JVM -- 垃圾回收(三)

一、类文件结构

编写一个简单的 HelloWorld.java 文件,将其编译为 HelloWorld.class 文件

HelloWorld.java 文件

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

HelloWorld.class 文件

package com.mycompany;
 
public class HelloWorld {
    public HelloWorld() {
    }
 
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

在Linux上执行  od -t xC HelloWorld.class 命令,输出如下

[root@eslinux1 jvmdemo]# od -t xC HelloWorld.class
0000000 ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09
0000020 00 15 00 16 08 00 17 0a 00 18 00 19 07 00 1a 07
0000040 00 1b 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29
0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e
0000100 75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63
0000120 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01
0000140 00 04 74 68 69 73 01 00 1a 4c 63 6f 6d 2f 6d 79
0000160 63 6f 6d 70 61 6e 79 2f 48 65 6c 6c 6f 57 6f 72
0000200 6c 64 3b 01 00 04 6d 61 69 6e 01 00 16 28 5b 4c
0000220 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67
0000240 3b 29 56 01 00 04 61 72 67 73 01 00 13 5b 4c 6a
0000260 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b
0000300 01 00 0a 53 6f 75 72 63 65 46 69 6c 65 01 00 0f
0000320 48 65 6c 6c 6f 57 6f 72 6c 64 2e 6a 61 76 61 0c
0000340 00 07 00 08 07 00 1c 0c 00 1d 00 1e 01 00 0c 48
0000360 65 6c 6c 6f 20 57 6f 72 6c 64 21 07 00 1f 0c 00
0000400 20 00 21 01 00 18 63 6f 6d 2f 6d 79 63 6f 6d 70
0000420 61 6e 79 2f 48 65 6c 6c 6f 57 6f 72 6c 64 01 00
0000440 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63
0000460 74 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 53 79
0000500 73 74 65 6d 01 00 03 6f 75 74 01 00 15 4c 6a 61
0000520 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61
0000540 6d 3b 01 00 13 6a 61 76 61 2f 69 6f 2f 50 72 69
0000560 6e 74 53 74 72 65 61 6d 01 00 07 70 72 69 6e 74
0000600 6c 6e 01 00 15 28 4c 6a 61 76 61 2f 6c 61 6e 67
0000620 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00 06
0000640 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00 09
0000660 00 00 00 2f 00 01 00 01 00 00 00 05 2a b7 00 01
0000700 b1 00 00 00 02 00 0a 00 00 00 06 00 01 00 00 00
0000720 03 00 0b 00 00 00 0c 00 01 00 00 00 05 00 0c 00
0000740 0d 00 00 00 09 00 0e 00 0f 00 01 00 09 00 00 00
0000760 37 00 02 00 01 00 00 00 09 b2 00 02 12 03 b6 00
0001000 04 b1 00 00 00 02 00 0a 00 00 00 0a 00 02 00 00
0001020 00 05 00 08 00 06 00 0b 00 00 00 0c 00 01 00 00
0001040 00 09 00 10 00 11 00 00 00 01 00 12 00 00 00 02
0001060 00 13
0001062

JVM规范官网

Java SE Specifications

如JDK8

Chapter 4. The class File Format

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

1、魔数

0~3 字节,表示它是否是【class】类型的文件

0000000 ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09

2、版本

4~7 字节,表示类的版本 00 34(52) 表示是 Java 8

0000000 ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09

3、常量池

Chapter 4. The class File Format

Constant TypeValue
CONSTANT_Class7
CONSTANT_Fieldref9
CONSTANT_Methodref10
CONSTANT_InterfaceMethodref11
CONSTANT_String8
CONSTANT_Integer3
CONSTANT_Float4
CONSTANT_Long5
CONSTANT_Double6
CONSTANT_NameAndType12
CONSTANT_Utf81
CONSTANT_MethodHandle15
CONSTANT_MethodType16
CONSTANT_InvokeDynamic18

8~9 字节,表示常量池长度,00 22 (34) 表示常量池有 #1~#33项,注意 #0 项不计入,也没有值

0000000 ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09

第#1项 0a 表示一个 Method 信息,00 06 和 00 14(20) 表示它引用了常量池中 #6 和 #21 项来获得 这个方法的【所属类】和【方法名】

0000000 ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09

第#2项 09 表示一个 Field 信息,00 15(21)和 00 16(22) 表示它引用了常量池中 #21 和 # 22 项 来获得这个成员变量的【所属类】和【成员变量名】

0000000 ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09
0000020 00 15 00 16 08 00 17 0a 00 18 00 19 07 00 1a 07

第#3项 08 表示一个字符串常量名称,00 18(23)表示它引用了常量池中 #23 项

0000020 00 15 00 16 08 00 17 0a 00 18 00 19 07 00 1a 07

第#4项 0a 表示一个 Method 信息,00 18(24) 和 00 19(25) 表示它引用了常量池中 #24 和 #25 项来获得这个方法的【所属类】和【方法名】

0000020 00 15 00 16 08 00 17 0a 00 18 00 19 07 00 1a 07

第#5项 07 表示一个 Class 信息,00 1a(26) 表示它引用了常量池中 #26 项

0000020 00 15 00 16 08 00 17 0a 00 18 00 19 07 00 1a 07

第#6项 07 表示一个 Class 信息,00 1b(27) 表示它引用了常量池中 #27 项

0000020 00 15 00 16 08 00 17 0a 00 18 00 19 07 00 1a 07
0000040 00 1b 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29

第#7项 01 表示一个 utf8 串,00 06 表示长度,3c 69 6e 69 74 3e 是【 】

0000040 00 1b 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29

第#8项 01 表示一个 utf8 串,00 03 表示长度,28 29 56 是【()V】其实就是表示无参、无返回值

0000040 00 1b 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29
0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e

第#9项 01 表示一个 utf8 串,00 04 表示长度,43 6f 64 65 是【Code】

0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e

第#10项 01 表示一个 utf8 串,00 0f(15) 表示长度,4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 是【LineNumberTable】

0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e
0000100 75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63

第#11项 01 表示一个 utf8 串,00 12(18) 表示长度,4c 6f 63 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65是【LocalVariableTable】

0000100 75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63
0000120 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01

第#12项 01 表示一个 utf8 串,00 04 表示长度,74 68 69 73 是【this】

0000120 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01
0000140 00 04 74 68 69 73 01 00 1a 4c 63 6f 6d 2f 6d 79

第#13项 01 表示一个 utf8 串,00 1a(26) 表示长度,是【Lcn/mycompany/HelloWorld;】

0000140 00 04 74 68 69 73 01 00 1a 4c 63 6f 6d 2f 6d 79
0000160 63 6f 6d 70 61 6e 79 2f 48 65 6c 6c 6f 57 6f 72
0000200 6c 64 3b 01 00 04 6d 61 69 6e 01 00 16 28 5b 4c

第#14项 01 表示一个 utf8 串,00 04 表示长度,74 68 69 73 是【main】

0000200 6c 64 3b 01 00 04 6d 61 69 6e 01 00 16 28 5b 4c

第#15项 01 表示一个 utf8 串,00 16(22) 表示长度,是【([Ljava/lang/String;)V】其实就是参数为 字符串数组,无返回值

0000200 6c 64 3b 01 00 04 6d 61 69 6e 01 00 16 28 5b 4c
0000220 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67
0000240 3b 29 56 01 00 04 61 72 67 73 01 00 13 5b 4c 6a

第#16项 01 表示一个 utf8 串,00 04 表示长度,是【args】

0000240 3b 29 56 01 00 04 61 72 67 73 01 00 13 5b 4c 6a

第#17项 01 表示一个 utf8 串,00 13(19) 表示长度,是【[Ljava/lang/String;】

0000240 3b 29 56 01 00 04 61 72 67 73 01 00 13 5b 4c 6a
0000260 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b

第#18项 01 表示一个 utf8 串,00 0a(10) 表示长度,是【SourceFile】

0000300 01 00 0a 53 6f 75 72 63 65 46 69 6c 65 01 00 0f
0000320 48 65 6c 6c 6f 57 6f 72 6c 64 2e 6a 61 76 61 0c

第#20项 01 表示一个 utf8 串,00 0f(15) 表示长度,是【HelloWorld.java】

0000300 01 00 0a 53 6f 75 72 63 65 46 69 6c 65 01 00 0f
0000320 48 65 6c 6c 6f 57 6f 72 6c 64 2e 6a 61 76 61 0c

第#21项 0c 表示一个 【名+类型】,00 07 00 08 引用了常量池中#7 #8 两项

0000340 00 07 00 08 07 00 1c 0c 00 1d 00 1e 01 00 0c 48
0000360 65 6c 6c 6f 20 57 6f 72 6c 64 21 07 00 1f 0c 00

第#22项 07 表示一个 Class 信息,00 1c(28) 引用了常量池中#28 项

0000340 00 07 00 08 07 00 1c 0c 00 1d 00 1e 01 00 0c 48

第#23项 0c 表示一个 【名+类型】,00 1d(29) 00 1e (30)引用了常量池中 #29#30两项

0000340 00 07 00 08 07 00 1c 0c 00 1d 00 1e 01 00 0c 48

第#24项 01 表示一个 utf8 串,00 1c(12) 表示长度,是【hello world】

0000340 00 07 00 08 07 00 1c 0c 00 1d 00 1e 01 00 0c 48

0000360 65 6c 6c 6f 20 57 6f 72 6c 64 21 07 00 1f 0c 00

第#25项 07 表示一个 Class 信息,00 20(32) 引用了常量池中#32 项

0000360 65 6c 6c 6f 20 57 6f 72 6c 64 21 07 00 1f 0c 00

0000400 20 00 21 01 00 18 63 6f 6d 2f 6d 79 63 6f 6d 70

第#26项 0c 表示一个 【名+类型】,00 21(33) 00 22(34)引用了常量池中 #33 #34 两项 0000420 61 6e 79 2f 48 65 6c 6c 6f 57 6f 72 6c 64 01 00

第#27项 01 表示一个 utf8 串,00 1b(27) 表示长度,是【cn/mycompany/HelloWorld

0000420 61 6e 79 2f 48 65 6c 6c 6f 57 6f 72 6c 64 01 00

0000440 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63

0000460 74 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 53 79

第#28项 01 表示一个 utf8 串,00 10(16) 表示长度,是【java/lang/Object】

0000460 74 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 53 79
0000500 73 74 65 6d 01 00 03 6f 75 74 01 00 15 4c 6a 61

第#29项 01 表示一个 utf8 串,00 10(16) 表示长度,是【java/lang/System】

0000500 73 74 65 6d 01 00 03 6f 75 74 01 00 15 4c 6a 61
0000520 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61

第#30项 01 表示一个 utf8 串,00 03 表示长度,是【out】

0000520 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61
0000540 6d 3b 01 00 13 6a 61 76 61 2f 69 6f 2f 50 72 69

第#31项 01 表示一个 utf8 串,00 15(21) 表示长度,是【Ljava/io/PrintStream;】

0000540 6d 3b 01 00 13 6a 61 76 61 2f 69 6f 2f 50 72 69
0000560 6e 74 53 74 72 65 61 6d 01 00 07 70 72 69 6e 74

第#32项 01 表示一个 utf8 串,00 13(19) 表示长度,是【java/io/PrintStream】

0000560 6e 74 53 74 72 65 61 6d 01 00 07 70 72 69 6e 74
0000600 6c 6e 01 00 15 28 4c 6a 61 76 61 2f 6c 61 6e 67

第#33项 01 表示一个 utf8 串,00 07 表示长度,是【println】

0000620 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00 06

第#34项 01 表示一个 utf8 串,00 15(21) 表示长度,是【(Ljava/lang/String;)V】

0000620 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00 06

4、访问标识与继承信息

21 表示该 class 是一个类,公共的

0000620 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00 06

05 表示根据常量池中 #5 找到本类全限定名

0000620 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00 06

06 表示根据常量池中 #6 找到父类全限定名

0000620 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00 06

表示接口的数量,本类为 0

0000620 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00 06
0000640 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00 09

Chapter 4. The class File Format

Flag NameValueInterpretation
ACC_PUBLIC0x0001Declared public; may be accessed from outside its package.
ACC_PRIVATE0x0002Declared private; usable only within the defining class.
ACC_PROTECTED0x0004Declared protected; may be accessed within subclasses.
ACC_STATIC0x0008Declared static.
ACC_FINAL0x0010Declared final; never directly assigned to after object construction (JLS §17.5).
ACC_VOLATILE0x0040Declared volatile; cannot be cached.
ACC_TRANSIENT0x0080Declared transient; not written or read by a persistent object manager.
ACC_SYNTHETIC0x1000Declared synthetic; not present in the source code.
ACC_ENUM0x4000Declared as an element of an enum.

5、Field 信息

表示成员变量数量,本类为 0

0000640 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00 09

Chapter 4. The class File Format

FieldType termTypeInterpretation
Bbytesigned byte
CcharUnicode character code point in the Basic Multilingual Plane, encoded with UTF-16
Ddoubledouble-precision floating-point value
Ffloatsingle-precision floating-point value
Iintinteger
Jlonglong integer
L ClassName ;referencean instance of class ClassName
Sshortsigned short
Zbooleantrue or false
[referenceone array dimension

6、Method 信息

表示方法数量,本类为 2

0000640 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00 09

0000640 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00 09
0000660 00 00 00 2f 00 01 00 01 00 00 00 05 2a b7 00 01
0000700 b1 00 00 00 02 00 0a 00 00 00 06 00 01 00 00 00
0000720 03 00 0b 00 00 00 0c 00 01 00 00 00 05 00 0c 00
0000740 0d 00 00 00 09 00 0e 00 0f 00 01 00 09 00 00 00

一个方法由 访问修饰符,名称,参数描述,方法属性数量,方法属性组成

红色代表访问修饰符(本类中是 public)

蓝色代表引用了常量池 #07 项作为方法名称

绿色代表引用了常量池 #08 项作为方法参数描述

黄色代表方法属性数量,本方法是 1

红色代表方法属性

(1)00 09 表示引用了常量池 #09 项,发现是【Code】属性

(2)00 00 00 2f 表示此属性的长度是 47

(3)00 01 表示【操作数栈】最大深度

(4)00 01 表示【局部变量表】最大槽(slot)数

00 00 00 05 表示字节码长度,本例是 5

2a b7 00 01 b1 是字节码指令

00 00 00 02 表示方法细节属性数量,本例是 2

00 0a 表示引用了常量池 #10 项,发现是【LineNumberTable】属性

00 00 00 06 表示此属性的总长度,本例是 6

00 01 表示【LineNumberTable】长度

00 00 表示【字节码】行号 00 04 表示【java 源码】行号

00 0b 表示引用了常量池 #11 项,发现是【LocalVariableTable】属性

00 00 00 0c 表示此属性的总长度,本例是 12

00 01 表示【LocalVariableTable】长度

00 00 表示局部变量生命周期开始,相对于字节码的偏移量

00 05 表示局部变量覆盖的范围长度

00 0c 表示局部变量名称,本例引用了常量池 #12 项,是【this】

00 0d 表示局部变量的类型,本例引用了常量池 #13 项,是 【Lcn/mycompany/HelloWorld;】

00 00 表示局部变量占有的槽位(slot)编号,本例是 0

二、字节码指令

(一)字节码指令基础

Chapter 6. The Java Virtual Machine Instruction Set

(1)public com.mycompany.HelloWorld();

构造方法的字节码指令

2a b7 00 01 b1

2a => aload_0 加载 slot 0 的局部变量,即 this,作为下面的 invokespecial 构造方法调用的参数

b7 => invokespecial,预备调用构造方法

00 01 引用常量池中 #1 项,即【 Method java/lang/Object."":()V 】

b1 表示返回

(2)public static void main(java.lang.String[]);

主方法的字节码指令

b2 00 02 12 03 b6 00 04 b1

b2 => getstatic 用来加载静态变量

00 02 引用常量池中 #2 项,即【Field java/lang/System.out:Ljava/io/PrintStream;】

12 => ldc 加载参数

03 引用常量池中 #3 项,即 【String hello world】

b6 => invokevirtual,预备调用成员方法

00 04 引用常量池中 #4 项,即【Method java/io/PrintStream.println:(Ljava/lang/String;)V】

b1 表示返回 

(二)javap 工具

Oracle 提供了 javap 工具来反编译 class 文件

首先切换到 HelloWorld.class文件所在目录,执行如下命令

javap -v HelloWorld.class
cd D:\Java\JavaProject\xx\com\mycompany

javap -v HelloWorld.class

控制台输出如下

Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/HelloWorld.class    //类文件名字以及路径
  Last modified 2021-9-27; size 562 bytes                //最后修改时间
  MD5 checksum 56139c042931911e7cea84a4ece0987c
  Compiled from "HelloWorld.java"        //源文件;从哪个java文件编译而来
public class com.mycompany.HelloWorld
  minor version: 0
  major version: 52    //JDK版本号,52代表JDK8
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:    //常量池
   //#1:方法引用: 引用了第6项和20项, Object类 init方法
   //#2:成员变量引用:引用了21 和 22 项 out变量+Pri–类型
   //#3: 字符串 : 对应Hello World!
   //#4:方法引用,引用了24 和 25 项 Pri–类型+println方法
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            // Hello World!
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // com/mycompany/HelloWorld
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/mycompany/HelloWorld;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               HelloWorld.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8               Hello World!
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               com/mycompany/HelloWorld
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public com.mycompany.HelloWorld();
    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/mycompany/HelloWorld;

  //main方法的分析
  //参数类型是字符串数组
  //访问修饰符号:public static
  //code:方法属性
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      //stack:最大操作数栈的深度
      //locals:局部变量表的长度
      //args_ize:参数长度
      //aload_0:把局部变量的第0项加载到操作数栈
      //0 3 5代表的是字节码的代码行号
      stack=2, locals=1, args_size=1
         //#2 代表引用常量池中的 第2项 #2 
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello World!
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      //line 5 : 0;5:源文件中的行号 对应 0:字节码中的行号
      LineNumberTable:
        line 5: 0
        line 6: 8
      //LocalVariableTable:本地变量表;变量的作用范围9行 0-8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"

(三)class文件执行流程

Chapter 6. The Java Virtual Machine Instruction Set

java源文件

/**
 * 字节码指令 和 操作数栈、常量池的关系
 */
public class ClassFile1 {
    public static void main(String[] args) {
        int a = 10;
        int b = Short.MAX_VALUE + 1;
        int c = a + b;
        System.out.println(c);
    }
}

执行 javap -v class文件名.class 反编译class文件 

Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/classfile/ClassFile1.class
  Last modified 2022-10-8; size 640 bytes
  MD5 checksum e12dc813bd8300b8e972e26436bb6a34
  Compiled from "ClassFile1.java"
public class com.mycompany.classfile.ClassFile1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#25         // java/lang/Object."<init>":()V
   #2 = Class              #26            // java/lang/Short
   #3 = Integer            32768
   #4 = Fieldref           #27.#28        // java/lang/System.out:Ljava/io/PrintStream;
   #5 = Methodref          #29.#30        // java/io/PrintStream.println:(I)V
   #6 = Class              #31            // com/mycompany/classfile/ClassFile1
   #7 = Class              #32            // java/lang/Object
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               LocalVariableTable
  #13 = Utf8               this
  #14 = Utf8               Lcom/mycompany/classfile/ClassFile1;
  #15 = Utf8               main
  #16 = Utf8               ([Ljava/lang/String;)V
  #17 = Utf8               args
  #18 = Utf8               [Ljava/lang/String;
  #19 = Utf8               a
  #20 = Utf8               I
  #21 = Utf8               b
  #22 = Utf8               c
  #23 = Utf8               SourceFile
  #24 = Utf8               ClassFile1.java
  #25 = NameAndType        #8:#9          // "<init>":()V
  #26 = Utf8               java/lang/Short
  #27 = Class              #33            // java/lang/System
  #28 = NameAndType        #34:#35        // out:Ljava/io/PrintStream;
  #29 = Class              #36            // java/io/PrintStream
  #30 = NameAndType        #37:#38        // println:(I)V
  #31 = Utf8               com/mycompany/classfile/ClassFile1
  #32 = Utf8               java/lang/Object
  #33 = Utf8               java/lang/System
  #34 = Utf8               out
  #35 = Utf8               Ljava/io/PrintStream;
  #36 = Utf8               java/io/PrintStream
  #37 = Utf8               println
  #38 = Utf8               (I)V
{
  public com.mycompany.classfile.ClassFile1();
    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 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/mycompany/classfile/ClassFile1;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: bipush        10
         2: istore_1
         3: ldc           #3                  // int 32768
         5: istore_2
         6: iload_1
         7: iload_2
         8: iadd
         9: istore_3
        10: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        13: iload_3
        14: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        17: return
      LineNumberTable:
        line 8: 0
        line 9: 3
        line 10: 6
        line 11: 10
        line 12: 17
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      18     0  args   [Ljava/lang/String;
            3      15     1     a   I
            6      12     2     b   I
           10       8     3     c   I
}
SourceFile: "ClassFile1.java"

1、常量池载入运行时常量池

2、方法字节码载入方法区

3、main 线程开始运行,分配栈帧内存

stack=2, locals=4

4、执行引擎开始执行字节码

(1)bipush 10

1、将一个 byte 压入操作数栈(其长度会补齐 4 个字节),类似的指令还有sipush

2、sipush 将一个 short 压入操作数栈(其长度会补齐 4 个字节)

3、ldc 将一个 int 压入操作数栈

4、ldc2_w 将一个 long 压入操作数栈(分两次压入,因为 long 是 8 个字节)

这里小的数字都是和字节码指令存在一起,超过 short 范围的数字存入了常量池

(2)istore_1

将操作数栈顶数据弹出,存入局部变量表的 slot 1

(3)ldc #3

从常量池加载 #3 数据到操作数栈

注:

Short.MAX_VALUE 是 32767,所以 32768 = Short.MAX_VALUE + 1 实际是在编译期间计算好

(4)istore_2

(5)iload_1

(6)iload_2

 

(7)iadd

(8)istore_3

(9)getstatic #4

 

(10)iload_3

(11)invokevirtual #5

找到常量池 #5 项

定位到方法区 java/io/PrintStream.println:(I)V 方法

生成新的栈帧(分配 locals、stack等)

传递参数,执行新栈帧中的字节码

执行完毕,弹出栈帧

清除 main 操作数栈内容

(12)return

完成 main 方法调用,弹出 main 栈帧

程序结束

(四)条件判断指令

指令助记符含义
0x99ifeq判断是否 == 0
0x9aifne判断是否 != 0
0x9biflt判断是否 < 0
0x9cifge判断是否 >= 0
0x9difgt判断是否 > 0
0x9eifle判断是否 <= 0
0x9fif_icmpeq两个int是否 ==
0xa0if_icmpne两个int是否 !=
0xa1if_icmplt两个int是否 <
0xa2if_icmpge两个int是否 >=
0xa3if_icmpgt两个int是否 >
0xa4if_icmple两个int是否 <=
0xa5if_acmpeq两个引用是否 ==
0xa6if_acmpne两个引用是否 !=
0xc6ifnull判断是否 == null
0xc7ifnonnull判断是否 != null

byte,short,char 都会按 int 比较,因为操作数栈都是 4 字节

goto 用来进行跳转到指定行号的字节码

Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/classfile/ClassFile3.class
  Last modified 2022-10-8; size 499 bytes
  MD5 checksum d1fc4d3f4e62927827257e4746785ca6
  Compiled from "ClassFile3.java"
public class com.mycompany.classfile.ClassFile3
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#20         // java/lang/Object."<init>":()V
   #2 = Class              #21            // com/mycompany/classfile/ClassFile3
   #3 = Class              #22            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               LocalVariableTable
   #9 = Utf8               this
  #10 = Utf8               Lcom/mycompany/classfile/ClassFile3;
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               args
  #14 = Utf8               [Ljava/lang/String;
  #15 = Utf8               a
  #16 = Utf8               I
  #17 = Utf8               StackMapTable
  #18 = Utf8               SourceFile
  #19 = Utf8               ClassFile3.java
  #20 = NameAndType        #4:#5          // "<init>":()V
  #21 = Utf8               com/mycompany/classfile/ClassFile3
  #22 = Utf8               java/lang/Object
{
  public com.mycompany.classfile.ClassFile3();
    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 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/mycompany/classfile/ClassFile3;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: ifne          12
         6: bipush        10
         8: istore_1
         9: goto          15
        12: bipush        20
        14: istore_1
        15: return
      LineNumberTable:
        line 8: 0
        line 9: 2
        line 10: 6
        line 12: 12
        line 14: 15
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0  args   [Ljava/lang/String;
            2      14     1     a   I
      StackMapTable: number_of_entries = 2
        frame_type = 252 /* append */
          offset_delta = 12
          locals = [ int ]
        frame_type = 2 /* same */
}
SourceFile: "ClassFile3.java"

(五)循环控制指令

Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/classfile/ClassFile4.class
  Last modified 2022-10-8; size 494 bytes
  MD5 checksum 7699a41df768a57866579ec358e6363e
  Compiled from "ClassFile4.java"
public class com.mycompany.classfile.ClassFile4
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#20         // java/lang/Object."<init>":()V
   #2 = Class              #21            // com/mycompany/classfile/ClassFile4
   #3 = Class              #22            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               LocalVariableTable
   #9 = Utf8               this
  #10 = Utf8               Lcom/mycompany/classfile/ClassFile4;
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               args
  #14 = Utf8               [Ljava/lang/String;
  #15 = Utf8               a
  #16 = Utf8               I
  #17 = Utf8               StackMapTable
  #18 = Utf8               SourceFile
  #19 = Utf8               ClassFile4.java
  #20 = NameAndType        #4:#5          // "<init>":()V
  #21 = Utf8               com/mycompany/classfile/ClassFile4
  #22 = Utf8               java/lang/Object
{
  public com.mycompany.classfile.ClassFile4();
    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 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/mycompany/classfile/ClassFile4;

  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: iconst_0
         1: istore_1
         2: iload_1
         3: bipush        10
         5: if_icmpge     14
         8: iinc          1, 1
        11: goto          2
        14: return
      LineNumberTable:
        line 8: 0
        line 9: 2
        line 10: 8
        line 12: 14
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  args   [Ljava/lang/String;
            2      13     1     a   I
      StackMapTable: number_of_entries = 2
        frame_type = 252 /* append */
          offset_delta = 2
          locals = [ int ]
        frame_type = 11 /* same */
}
SourceFile: "ClassFile4.java"

(六)构造方法 

1、<cinit>()V

public class ClassFile7 {
    static int i = 20;

    public ClassFile7() {
    }

    public static void main(String[] args) {
        System.out.println(i);
    }

    static {
        i = 30;
        i = 10;
    }
}
Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/classfile/ClassFile7.class
  Last modified 2022-10-8; size 647 bytes
  MD5 checksum 7db17d47259672d9fa7018e8ae0488d7
  Compiled from "ClassFile7.java"
public class com.mycompany.classfile.ClassFile7
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#23         // java/lang/Object."<init>":()V
   #2 = Fieldref           #24.#25        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Fieldref           #5.#26         // com/mycompany/classfile/ClassFile7.i:I
   #4 = Methodref          #27.#28        // java/io/PrintStream.println:(I)V
   #5 = Class              #29            // com/mycompany/classfile/ClassFile7
   #6 = Class              #30            // java/lang/Object
   #7 = Utf8               i
   #8 = Utf8               I
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               LocalVariableTable
  #14 = Utf8               this
  #15 = Utf8               Lcom/mycompany/classfile/ClassFile7;
  #16 = Utf8               main
  #17 = Utf8               ([Ljava/lang/String;)V
  #18 = Utf8               args
  #19 = Utf8               [Ljava/lang/String;
  #20 = Utf8               <clinit>
  #21 = Utf8               SourceFile
  #22 = Utf8               ClassFile7.java
  #23 = NameAndType        #9:#10         // "<init>":()V
  #24 = Class              #31            // java/lang/System
  #25 = NameAndType        #32:#33        // out:Ljava/io/PrintStream;
  #26 = NameAndType        #7:#8          // i:I
  #27 = Class              #34            // java/io/PrintStream
  #28 = NameAndType        #35:#36        // println:(I)V
  #29 = Utf8               com/mycompany/classfile/ClassFile7
  #30 = Utf8               java/lang/Object
  #31 = Utf8               java/lang/System
  #32 = Utf8               out
  #33 = Utf8               Ljava/io/PrintStream;
  #34 = Utf8               java/io/PrintStream
  #35 = Utf8               println
  #36 = Utf8               (I)V
{
  static int i;
    descriptor: I
    flags: ACC_STATIC

  public com.mycompany.classfile.ClassFile7();
    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 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/mycompany/classfile/ClassFile7;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: getstatic     #3                  // Field i:I
         6: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
         9: return
      LineNumberTable:
        line 18: 0
        line 19: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  args   [Ljava/lang/String;

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: bipush        20
         2: putstatic     #3                  // Field i:I
         5: bipush        30
         7: putstatic     #3                  // Field i:I
        10: bipush        10
        12: putstatic     #3                  // Field i:I
        15: return
      LineNumberTable:
        line 8: 0
        line 12: 5
        line 15: 10
}

编译器会按从上至下的顺序,收集所有 static 静态代码块和静态成员赋值的代码,合并为一个特殊的方法 <cinit>()V 

<cinit>()V 方法会在类加载的初始化阶段被调用

2、<init>()V

public class ClassFile8 {
    private String a = "s1";

    {
        b = 20;
    }

    private int b = 10;

    {
        a = "s2";
    }

    public ClassFile8(String a, int b) {
        this.a = a;
        this.b = b;
    }

    public static void main(String[] args) {
        ClassFile8 d = new ClassFile8("s3", 30);
        System.out.println(d.a);
        System.out.println(d.b);
    }
}
Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/classfile/ClassFile8.class
  Last modified 2022-10-8; size 837 bytes
  MD5 checksum 8e7d6afa5949cdbee15d459a0ff1e708
  Compiled from "ClassFile8.java"
public class com.mycompany.classfile.ClassFile8
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #12.#31        // java/lang/Object."<init>":()V
   #2 = String             #32            // s1
   #3 = Fieldref           #6.#33         // com/mycompany/classfile/ClassFile8.a:Ljava/lang/String;
   #4 = Fieldref           #6.#34         // com/mycompany/classfile/ClassFile8.b:I
   #5 = String             #35            // s2
   #6 = Class              #36            // com/mycompany/classfile/ClassFile8
   #7 = String             #37            // s3
   #8 = Methodref          #6.#38         // com/mycompany/classfile/ClassFile8."<init>":(Ljava/lang/String;I)V
   #9 = Fieldref           #39.#40        // java/lang/System.out:Ljava/io/PrintStream;
  #10 = Methodref          #41.#42        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #11 = Methodref          #41.#43        // java/io/PrintStream.println:(I)V
  #12 = Class              #44            // java/lang/Object
  #13 = Utf8               a
  #14 = Utf8               Ljava/lang/String;
  #15 = Utf8               b
  #16 = Utf8               I
  #17 = Utf8               <init>
  #18 = Utf8               (Ljava/lang/String;I)V
  #19 = Utf8               Code
  #20 = Utf8               LineNumberTable
  #21 = Utf8               LocalVariableTable
  #22 = Utf8               this
  #23 = Utf8               Lcom/mycompany/classfile/ClassFile8;
  #24 = Utf8               main
  #25 = Utf8               ([Ljava/lang/String;)V
  #26 = Utf8               args
  #27 = Utf8               [Ljava/lang/String;
  #28 = Utf8               d
  #29 = Utf8               SourceFile
  #30 = Utf8               ClassFile8.java
  #31 = NameAndType        #17:#45        // "<init>":()V
  #32 = Utf8               s1
  #33 = NameAndType        #13:#14        // a:Ljava/lang/String;
  #34 = NameAndType        #15:#16        // b:I
  #35 = Utf8               s2
  #36 = Utf8               com/mycompany/classfile/ClassFile8
  #37 = Utf8               s3
  #38 = NameAndType        #17:#18        // "<init>":(Ljava/lang/String;I)V
  #39 = Class              #46            // java/lang/System
  #40 = NameAndType        #47:#48        // out:Ljava/io/PrintStream;
  #41 = Class              #49            // java/io/PrintStream
  #42 = NameAndType        #50:#51        // println:(Ljava/lang/String;)V
  #43 = NameAndType        #50:#52        // println:(I)V
  #44 = Utf8               java/lang/Object
  #45 = Utf8               ()V
  #46 = Utf8               java/lang/System
  #47 = Utf8               out
  #48 = Utf8               Ljava/io/PrintStream;
  #49 = Utf8               java/io/PrintStream
  #50 = Utf8               println
  #51 = Utf8               (Ljava/lang/String;)V
  #52 = Utf8               (I)V
{
  public com.mycompany.classfile.ClassFile8(java.lang.String, int);
    descriptor: (Ljava/lang/String;I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: ldc           #2                  // String s1
         7: putfield      #3                  // Field a:Ljava/lang/String;
        10: aload_0
        11: bipush        20
        13: putfield      #4                  // Field b:I
        16: aload_0
        17: bipush        10
        19: putfield      #4                  // Field b:I
        22: aload_0
        23: ldc           #5                  // String s2
        25: putfield      #3                  // Field a:Ljava/lang/String;
        28: aload_0
        29: aload_1
        30: putfield      #3                  // Field a:Ljava/lang/String;
        33: aload_0
        34: iload_2
        35: putfield      #4                  // Field b:I
        38: return
      LineNumberTable:
        line 19: 0
        line 7: 4
        line 10: 10
        line 13: 16
        line 16: 22
        line 20: 28
        line 21: 33
        line 22: 38
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      39     0  this   Lcom/mycompany/classfile/ClassFile8;
            0      39     1     a   Ljava/lang/String;
            0      39     2     b   I

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=2, args_size=1
         0: new           #6                  // class com/mycompany/classfile/ClassFile8
         3: dup
         4: ldc           #7                  // String s3
         6: bipush        30
         8: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
        11: astore_1
        12: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
        15: aload_1
        16: getfield      #3                  // Field a:Ljava/lang/String;
        19: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        22: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
        25: aload_1
        26: getfield      #4                  // Field b:I
        29: invokevirtual #11                 // Method java/io/PrintStream.println:(I)V
        32: return
      LineNumberTable:
        line 25: 0
        line 26: 12
        line 27: 22
        line 28: 32
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      33     0  args   [Ljava/lang/String;
           12      21     1     d   Lcom/mycompany/classfile/ClassFile8;
}
SourceFile: "ClassFile8.java"

编译器会按从上至下的顺序,收集所有 {} 代码块和成员变量赋值的代码,形成新的构造方法,但原始构 造方法内的代码总是在最后

(七)方法调用

public class ClassFile9 {
    public ClassFile9() { }

    private void test1() { }

    private final void test2() { }

    public void test3() { }

    public static void test4() { }

    @Override
    public String toString() {
        return super.toString();
    }

    public static void main(String[] args) {
        ClassFile9 d = new ClassFile9();
        d.test1();
        d.test2();
        d.test3();
        d.test4();
        ClassFile9.test4();
        d.toString();
    }
}
Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/classfile/ClassFile9.class
  Last modified 2022-10-8; size 893 bytes
  MD5 checksum ff6e5ab3e67ae93660e40cb67828777d
  Compiled from "ClassFile9.java"
public class com.mycompany.classfile.ClassFile9
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #10.#31        // java/lang/Object."<init>":()V
   #2 = Methodref          #10.#32        // java/lang/Object.toString:()Ljava/lang/String;
   #3 = Class              #33            // com/mycompany/classfile/ClassFile9
   #4 = Methodref          #3.#31         // com/mycompany/classfile/ClassFile9."<init>":()V
   #5 = Methodref          #3.#34         // com/mycompany/classfile/ClassFile9.test1:()V
   #6 = Methodref          #3.#35         // com/mycompany/classfile/ClassFile9.test2:()V
   #7 = Methodref          #3.#36         // com/mycompany/classfile/ClassFile9.test3:()V
   #8 = Methodref          #3.#37         // com/mycompany/classfile/ClassFile9.test4:()V
   #9 = Methodref          #3.#32         // com/mycompany/classfile/ClassFile9.toString:()Ljava/lang/String;
  #10 = Class              #38            // java/lang/Object
  #11 = Utf8               <init>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               LocalVariableTable
  #16 = Utf8               this
  #17 = Utf8               Lcom/mycompany/classfile/ClassFile9;
  #18 = Utf8               test1
  #19 = Utf8               test2
  #20 = Utf8               test3
  #21 = Utf8               test4
  #22 = Utf8               toString
  #23 = Utf8               ()Ljava/lang/String;
  #24 = Utf8               main
  #25 = Utf8               ([Ljava/lang/String;)V
  #26 = Utf8               args
  #27 = Utf8               [Ljava/lang/String;
  #28 = Utf8               d
  #29 = Utf8               SourceFile
  #30 = Utf8               ClassFile9.java
  #31 = NameAndType        #11:#12        // "<init>":()V
  #32 = NameAndType        #22:#23        // toString:()Ljava/lang/String;
  #33 = Utf8               com/mycompany/classfile/ClassFile9
  #34 = NameAndType        #18:#12        // test1:()V
  #35 = NameAndType        #19:#12        // test2:()V
  #36 = NameAndType        #20:#12        // test3:()V
  #37 = NameAndType        #21:#12        // test4:()V
  #38 = Utf8               java/lang/Object
{
  public com.mycompany.classfile.ClassFile9();
    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/mycompany/classfile/ClassFile9;

  public void test3();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 13: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/mycompany/classfile/ClassFile9;

  public static void test4();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=0, locals=0, args_size=0
         0: return
      LineNumberTable:
        line 15: 0

  public java.lang.String toString();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #2                  // Method java/lang/Object.toString:()Ljava/lang/String;
         4: areturn
      LineNumberTable:
        line 19: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/mycompany/classfile/ClassFile9;

  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           #3                  // class com/mycompany/classfile/ClassFile9
         3: dup
         4: invokespecial #4                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: invokespecial #5                  // Method test1:()V
        12: aload_1
        13: invokespecial #6                  // Method test2:()V
        16: aload_1
        17: invokevirtual #7                  // Method test3:()V
        20: aload_1
        21: pop
        22: invokestatic  #8                  // Method test4:()V
        25: invokestatic  #8                  // Method test4:()V
        28: aload_1
        29: invokevirtual #9                  // Method toString:()Ljava/lang/String;
        32: pop
        33: return
      LineNumberTable:
        line 23: 0
        line 24: 8
        line 25: 12
        line 26: 16
        line 27: 20
        line 28: 25
        line 29: 28
        line 30: 33
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      34     0  args   [Ljava/lang/String;
            8      26     1     d   Lcom/mycompany/classfile/ClassFile9;
}
SourceFile: "ClassFile9.java"

1、new 是创建【对象】,给对象分配堆内存,执行成功会将【对象引用】压入操作数栈

2、dup 是赋值操作数栈栈顶的内容,本例即为【对象引用】,为什么需要两份引用呢,一个是要配 合 invokespecial 调用该对象的构造方法 "":()V (会消耗掉栈顶一个引用),另一个要 配合 astore_1 赋值给局部变量

3、最终方法(final),私有方法(private),构造方法都是由 invokespecial 指令来调用,属于静 态绑定

4、普通成员方法是由 invokevirtual 调用,属于动态绑定,即支持多态

5、成员方法与静态方法调用的另一个区别是,执行方法前是否需要【对象引用】

6、d.test4(); 是通过【对象引用】调用一个静态方法,可以看到在调用 invokestatic 之前执行了 pop 指令,把【对象引用】从操作数栈弹掉了

7、还有一个执行 invokespecial 的情况是通过 super 调用父类方法  

(八)多态原理

/**
 * 多态原理,JVM 参数,禁用指针压缩
 * -XX:-UseCompressedOops -XX:-UseCompressedClassPointers
 */
public class ClassFile10 {
    public static void test(Animal animal) {
        animal.eat();
        System.out.println(animal.toString());
    }

    public static void main(String[] args) throws IOException {
        test(new Cat());
        test(new Dog());
        System.in.read();
    }
}

abstract class Animal {
    public abstract void eat();

    @Override
    public String toString() {
        return "我是" + this.getClass().getSimpleName();
    }
}

class Dog extends Animal {

    @Override
    public void eat() {
        System.out.println("啃骨头");
    }
}

class Cat extends Animal {

    @Override
    public void eat() {
        System.out.println("吃鱼");
    }
}
Classfile /D:/Java/JavaProject/jvm-demo/myjvm/out/production/myjvm/com/mycompany/classfile/ClassFile10.class
  Last modified 2022-10-8; size 1079 bytes
  MD5 checksum 6bedd141f28a01d372823cac5f67f63a
  Compiled from "ClassFile10.java"
public class com.mycompany.classfile.ClassFile10
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #14.#34        // java/lang/Object."<init>":()V
   #2 = Methodref          #35.#36        // com/mycompany/classfile/Animal.eat:()V
   #3 = Fieldref           #37.#38        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = Methodref          #35.#39        // com/mycompany/classfile/Animal.toString:()Ljava/lang/String;
   #5 = Methodref          #40.#41        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #6 = Class              #42            // com/mycompany/classfile/Cat
   #7 = Methodref          #6.#34         // com/mycompany/classfile/Cat."<init>":()V
   #8 = Methodref          #13.#43        // com/mycompany/classfile/ClassFile10.test:(Lcom/mycompany/classfile/Animal;)V
   #9 = Class              #44            // com/mycompany/classfile/Dog
  #10 = Methodref          #9.#34         // com/mycompany/classfile/Dog."<init>":()V
  #11 = Fieldref           #37.#45        // java/lang/System.in:Ljava/io/InputStream;
  #12 = Methodref          #46.#47        // java/io/InputStream.read:()I
  #13 = Class              #48            // com/mycompany/classfile/ClassFile10
  #14 = Class              #49            // java/lang/Object
  #15 = Utf8               <init>
  #16 = Utf8               ()V
  #17 = Utf8               Code
  #18 = Utf8               LineNumberTable
  #19 = Utf8               LocalVariableTable
  #20 = Utf8               this
  #21 = Utf8               Lcom/mycompany/classfile/ClassFile10;
  #22 = Utf8               test
  #23 = Utf8               (Lcom/mycompany/classfile/Animal;)V
  #24 = Utf8               animal
  #25 = Utf8               Lcom/mycompany/classfile/Animal;
  #26 = Utf8               main
  #27 = Utf8               ([Ljava/lang/String;)V
  #28 = Utf8               args
  #29 = Utf8               [Ljava/lang/String;
  #30 = Utf8               Exceptions
  #31 = Class              #50            // java/io/IOException
  #32 = Utf8               SourceFile
  #33 = Utf8               ClassFile10.java
  #34 = NameAndType        #15:#16        // "<init>":()V
  #35 = Class              #51            // com/mycompany/classfile/Animal
  #36 = NameAndType        #52:#16        // eat:()V
  #37 = Class              #53            // java/lang/System
  #38 = NameAndType        #54:#55        // out:Ljava/io/PrintStream;
  #39 = NameAndType        #56:#57        // toString:()Ljava/lang/String;
  #40 = Class              #58            // java/io/PrintStream
  #41 = NameAndType        #59:#60        // println:(Ljava/lang/String;)V
  #42 = Utf8               com/mycompany/classfile/Cat
  #43 = NameAndType        #22:#23        // test:(Lcom/mycompany/classfile/Animal;)V
  #44 = Utf8               com/mycompany/classfile/Dog
  #45 = NameAndType        #61:#62        // in:Ljava/io/InputStream;
  #46 = Class              #63            // java/io/InputStream
  #47 = NameAndType        #64:#65        // read:()I
  #48 = Utf8               com/mycompany/classfile/ClassFile10
  #49 = Utf8               java/lang/Object
  #50 = Utf8               java/io/IOException
  #51 = Utf8               com/mycompany/classfile/Animal
  #52 = Utf8               eat
  #53 = Utf8               java/lang/System
  #54 = Utf8               out
  #55 = Utf8               Ljava/io/PrintStream;
  #56 = Utf8               toString
  #57 = Utf8               ()Ljava/lang/String;
  #58 = Utf8               java/io/PrintStream
  #59 = Utf8               println
  #60 = Utf8               (Ljava/lang/String;)V
  #61 = Utf8               in
  #62 = Utf8               Ljava/io/InputStream;
  #63 = Utf8               java/io/InputStream
  #64 = Utf8               read
  #65 = Utf8               ()I
{
  public com.mycompany.classfile.ClassFile10();
    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 9: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/mycompany/classfile/ClassFile10;

  public static void test(com.mycompany.classfile.Animal);
    descriptor: (Lcom/mycompany/classfile/Animal;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokevirtual #2                  // Method com/mycompany/classfile/Animal.eat:()V
         4: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: aload_0
         8: invokevirtual #4                  // Method com/mycompany/classfile/Animal.toString:()Ljava/lang/String;
        11: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        14: return
      LineNumberTable:
        line 11: 0
        line 12: 4
        line 13: 14
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0 animal   Lcom/mycompany/classfile/Animal;

  public static void main(java.lang.String[]) throws java.io.IOException;
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: new           #6                  // class com/mycompany/classfile/Cat
         3: dup
         4: invokespecial #7                  // Method com/mycompany/classfile/Cat."<init>":()V
         7: invokestatic  #8                  // Method test:(Lcom/mycompany/classfile/Animal;)V
        10: new           #9                  // class com/mycompany/classfile/Dog
        13: dup
        14: invokespecial #10                 // Method com/mycompany/classfile/Dog."<init>":()V
        17: invokestatic  #8                  // Method test:(Lcom/mycompany/classfile/Animal;)V
        20: getstatic     #11                 // Field java/lang/System.in:Ljava/io/InputStream;
        23: invokevirtual #12                 // Method java/io/InputStream.read:()I
        26: pop
        27: return
      LineNumberTable:
        line 16: 0
        line 17: 10
        line 18: 20
        line 19: 27
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      28     0  args   [Ljava/lang/String;
    Exceptions:
      throws java.io.IOException
}
SourceFile: "ClassFile10.java"

1、运行代码

停在 System.in.read() 方法上,这时运行 jps 获取进程 id

jps
20928 Jps
19348 ClassFile10
3412 Launcher
2296 RemoteMavenServer36
3084

2、运行 HSDB 工具

C:

// 进入 JDK 安装目录,执行
C:\>cd C:\Users\zhangm\.jdks\corretto-1.8.0_342

java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB

进入图形界面 attach 进程 id 

输入进程号 即可  

如果遇到报错 Exception in thread "Thread-1" java.lang.UnsatisfiedLinkError: Can't load library: D:\JDK\jre1.8.0_291\bin\sawindbg.dll

Exception in thread "Thread-1" java.lang.UnsatisfiedLinkError: Can't load library: D:\JDK\jre1.8.0_291\bin\sawindbg.dll

将 D:\JDK\jdk1.8.0_291\jre\bin 目录下 sawindbg.dll  粘贴到 D:\JDK\jre1.8.0_291\bin 目录下 即可

3、查找对象 

打开 Tools -> Find Object By Query

输入 select d from com.mycompany.classfile.Dog d 点击 Execute 执行

4、查看对象内存结构 

点击超链接可以看到对象的内存结构,此对象没有任何属性,因此只有对象头的 16 字节,前 8 字节是 MarkWord,后 8 字节就是对象的 Class 指针

目前看不到它的实际地址

5、查看对象 Class 的内存地址

以通过 Windows -> Console 进入命令行模式,执行

mem 0x0000000741909d20 2

mem 有两个参数,参数 1 是对象地址,参数 2 是查看 2 行(即 16 字节) 

结果中第二行 0x00000000f800c444 即为 Class 的内存地址

6、查看类的 vtable

方法一:Alt+R 进入 Inspector 工具,输入刚才的 Class 内存地址,看到如下界面 

方法二:Tools -> Class Browser 输入 Dog 查找

Dog Class 的 vtable 长度为 6,意思就是 Dog 类有 6 个虚方法(多态 相关的,final,static 不会列入)

从 Class 的起始地址开始算,偏移 0x1b8 就是 vtable 的起始地址

7、验证方法地址

通过 Tools -> Class Browser 查看每个类的方法定义

8、当执行 invokevirtual 指令时

(1)先通过栈帧中的对象引用找到对象

(2)分析对象头,找到对象的实际 Class

(3)Class 结构中有 vtable,它在类加载的链接阶段就已经根据方法的重写规则生成好了

(4)查表得到方法的具体地址

(5)执行方法的字节码

(九)异常处理

public static void main(String[] args) {
        int i = 0;
        try {
            i = 10;
        } catch (Exception e) {
            i = 20;
        }
    }
 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: bipush        10
         4: istore_1
         5: goto          12
         8: astore_2
         9: bipush        20
        11: istore_1
        12: return
      Exception table:
         from    to  target type
             2     5     8   Class java/lang/Exception
      LineNumberTable:
        line 5: 0
        line 7: 2
        line 10: 5
        line 8: 8
        line 9: 9
        line 11: 12
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            9       3     2     e   Ljava/lang/Exception;
            0      13     0  args   [Ljava/lang/String;
            2      11     1     i   I
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 8
          locals = [ class "[Ljava/lang/String;", int ]
          stack = [ class java/lang/Exception ]
        frame_type = 3 /* same */
}

多出来一个 Exception table 的结构,[from, to) 是前闭后开的检测范围,一旦这个范围 内的字节码执行出现异常,则通过 type 匹配异常类型,如果一致,进入 target 所指示行号

8 行的字节码指令 astore_2 是将异常对象引用存入局部变量表的 slot 2 位置

1、多个 single-catch 块

public static void main(String[] args) {
        int i = 0;
        try {
            i = 10;
        } catch (ArithmeticException e) {
            i = 30;
        } catch (NullPointerException e) {
            i = 40;
        } catch (Exception e) {
            i = 50;
        }
    }
public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: bipush        10
         4: istore_1
         5: goto          26
         8: astore_2
         9: bipush        30
        11: istore_1
        12: goto          26
        15: astore_2
        16: bipush        40
        18: istore_1
        19: goto          26
        22: astore_2
        23: bipush        50
        25: istore_1
        26: return
      Exception table:
         from    to  target type
             2     5     8   Class java/lang/ArithmeticException
             2     5    15   Class java/lang/NullPointerException
             2     5    22   Class java/lang/Exception
      LineNumberTable:
        line 5: 0
        line 7: 2
        line 14: 5
        line 8: 8
        line 9: 9
        line 14: 12
        line 10: 15
        line 11: 16
        line 14: 19
        line 12: 22
        line 13: 23
        line 15: 26
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            9       3     2     e   Ljava/lang/ArithmeticException;
           16       3     2     e   Ljava/lang/NullPointerException;
           23       3     2     e   Ljava/lang/Exception;
            0      27     0  args   [Ljava/lang/String;
            2      25     1     i   I
      StackMapTable: number_of_entries = 4
        frame_type = 255 /* full_frame */
          offset_delta = 8
          locals = [ class "[Ljava/lang/String;", int ]
          stack = [ class java/lang/ArithmeticException ]
        frame_type = 70 /* same_locals_1_stack_item */
          stack = [ class java/lang/NullPointerException ]
        frame_type = 70 /* same_locals_1_stack_item */
          stack = [ class java/lang/Exception ]
        frame_type = 3 /* same */

2、multi-catch 的情况

public static void main(String[] args) {
        try {
            Method test = ClassFile13.class.getMethod("test");
            test.invoke(null);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    public static void test() {
        System.out.println("ok");
    }
 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: ldc           #2                  // class com/mycompany/classfile/ClassFile13
         2: ldc           #3                  // String test
         4: iconst_0
         5: anewarray     #4                  // class java/lang/Class
         8: invokevirtual #5                  // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
        11: astore_1
        12: aload_1
        13: aconst_null
        14: iconst_0
        15: anewarray     #6                  // class java/lang/Object
        18: invokevirtual #7                  // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
        21: pop
        22: goto          30
        25: astore_1
        26: aload_1
        27: invokevirtual #11                 // Method java/lang/ReflectiveOperationException.printStackTrace:()V
        30: return
      Exception table:
         from    to  target type
             0    22    25   Class java/lang/NoSuchMethodException
             0    22    25   Class java/lang/IllegalAccessException
             0    22    25   Class java/lang/reflect/InvocationTargetException
      LineNumberTable:
        line 9: 0
        line 10: 12
        line 13: 22
        line 11: 25
        line 12: 26
        line 14: 30
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           12      10     1  test   Ljava/lang/reflect/Method;
           26       4     1     e   Ljava/lang/ReflectiveOperationException;
            0      31     0  args   [Ljava/lang/String;
      StackMapTable: number_of_entries = 2
        frame_type = 89 /* same_locals_1_stack_item */
          stack = [ class java/lang/ReflectiveOperationException ]
        frame_type = 4 /* same */

  public static void test();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #13                 // String ok
         5: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 17: 0
        line 18: 8

3、finally

public static void main(String[] args) {
        int i = 0;
        try {
            i = 10;
        } catch (Exception e) {
            i = 20;
        } finally {
            i = 30;
        }
    }
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=4, args_size=1
0: iconst_0
1: istore_1 // 0 -> i
2: bipush 10 // try --------------------------------------
4: istore_1 // 10 -> i |
5: bipush 30 // finally |
7: istore_1 // 30 -> i |
8: goto 27 // return -----------------------------------
11: astore_2 // catch Exceptin -> e ----------------------
12: bipush 20 // |
14: istore_1 // 20 -> i |
15: bipush 30 // finally |
17: istore_1 // 30 -> i |
18: goto 27 // return -----------------------------------
21: astore_3 // catch any -> slot 3 ----------------------
22: bipush 30 // finally |
24: istore_1 // 30 -> i |
25: aload_3 // <- slot 3 |
26: athrow // throw ------------------------------------
27: return
Exception table:
from to target type
2 5 11 Class java/lang/Exception
2 5 21 any // 剩余的异常类型,比如 Error
11 15 21 any // 剩余的异常类型,比如 Error
LineNumberTable: ...
LocalVariableTable:
Start Length Slot Name Signature
12 3 2 e Ljava/lang/Exception;
0 28 0 args [Ljava/lang/String;
2 26 1 i I
StackMapTable: ...
MethodParameters: ...

finally 中的代码被复制了 3 份,分别放入 try 流程,catch 流程以及 catch 剩余的异常类型流 程

(十)synchronized

public static void main(String[] args) {
        Object lock = new Object();
        synchronized (lock) {
            System.out.println("ok");
        }
    }
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: new #2 // new Object
3: dup
4: invokespecial #1 // invokespecial <init>:()V
7: astore_1 // lock引用 -> lock
8: aload_1 // <- lock (synchronized开始)
9: dup
10: astore_2 // lock引用 -> slot 2
11: monitorenter // monitorenter(lock引用)
12: getstatic #3 // <- System.out
15: ldc #4 // <- "ok"
17: invokevirtual #5 // invokevirtual println:
(Ljava/lang/String;)V
20: aload_2 // <- slot 2(lock引用)
21: monitorexit // monitorexit(lock引用)
22: goto 30
25: astore_3 // any -> slot 3
26: aload_2 // <- slot 2(lock引用)
27: monitorexit // monitorexit(lock引用)
28: aload_3
29: athrow
30: return
Exception table:
from to target type
12 22 25 any
25 28 25 any
LineNumberTable: ...
LocalVariableTable:
Start Length Slot Name Signature
0 31 0 args [Ljava/lang/String;
8 23 1 lock Ljava/lang/Object;
StackMapTable: ...
MethodParameters: ...

注:

方法级别的 synchronized 不会在字节码指令中有所体现

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值