你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

JVM 字节码指令和java程序底层运行过程详解

2021/12/22 20:52:43

1.重要的相关参数

1.1常量池信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EzxN4Kk5-1640177014199)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211221234924955.png)]

1.2访问标识和继承信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q6XAmTB9-1640177014204)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211221235055079.png)]

1.3Field信息

在这里插入图片描述

2.字节码指令

2.1 入门

public cn.itcast.jvm.t5.HelloWorld(); 构造方法的字节码指令1. 2a => aload_0 加载 slot 0 的局部变量,即 this,做为下面的 invokespecial 构造方法调用的参数

\2. b7 => invokespecial 预备调用构造方法,哪个方法呢?

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

\4. b1 表示返回

另一个是 public static void main(java.lang.String[]); 主方法的字节码指令

\1. b2 => getstatic 用来加载静态变量,哪个静态变量呢?

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

\3. 12 => ldc 加载参数,哪个参数呢?

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

\5. b6 => invokevirtual 预备调用成员方法,哪个方法呢?

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

\7. b1 表示返回

请参考

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5

2.2 javap工具

自己分析类文件结构太麻烦了,Oracle 提供了 javap 工具来反编译 class 文件

2.3 图解方法执行流程

1)原始java 代码

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

2)反编译后的字节码文件

[root@localhost ~]# javap -v Demo3_1.class 
Classfile /root/Demo3_1.class 
Last modified Jul 7, 2019; size 665 bytes 
MD5 checksum a2c29a22421e218d4924d31e6990cfc5 
Compiled from "Demo3_1.java" 
public class cn.itcast.jvm.t3.bytecode.Demo3_1 
minor version: 0 
major version: 52 
flags: ACC_PUBLIC, ACC_SUPER 
Constant pool: 
\#1 = Methodref #7.#26 // java/lang/Object."<init>":()V 
\#2 = Class #27 // java/lang/Short 
\#3 = Integer 32768 
\#4 = Fieldref #28.#29 // 
java/lang/System.out:Ljava/io/PrintStream; 
\#5 = Methodref #30.#31 // java/io/PrintStream.println:(I)V 
\#6 = Class #32 // cn/itcast/jvm/t3/bytecode/Demo3_1 
\#7 = Class #33 // java/lang/Object 
\#8 = Utf8 <init> 
\#9 = Utf8 ()V 
\#10 = Utf8 Code 
\#11 = Utf8 LineNumberTable 
\#12 = Utf8 LocalVariableTable 
\#13 = Utf8 this 
\#14 = Utf8 Lcn/itcast/jvm/t3/bytecode/Demo3_1; 
\#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 MethodParameters 
\#24 = Utf8 SourceFile 
\#25 = Utf8 Demo3_1.java 
\#26 = NameAndType #8:#9 // "<init>":()V 
\#27 = Utf8 java/lang/Short 
\#28 = Class #34 // java/lang/System 
\#29 = NameAndType #35:#36 // out:Ljava/io/PrintStream; 
\#30 = Class #37 // java/io/PrintStream 
\#31 = NameAndType #38:#39 // println:(I)V 
\#32 = Utf8 cn/itcast/jvm/t3/bytecode/Demo3_1 
\#33 = Utf8 java/lang/Object 
\#34 = Utf8 java/lang/System 
\#35 = Utf8 out 
\#36 = Utf8 Ljava/io/PrintStream; 
\#37 = Utf8 java/io/PrintStream 
\#38 = Utf8 println 
\#39 = Utf8 (I)V 
{ 
public cn.itcast.jvm.t3.bytecode.Demo3_1(); 
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 Lcn/itcast/jvm/t3/bytecode/Demo3_1; 
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 
MethodParameters: 
Name Flags 
args 
}

3常量池载入运行时常量池

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JDwacVhp-1640177014213)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211221232850746.png)]

4.方法字节码载入方法区

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AEgyoU80-1640177014215)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211221233907625.png
)]

5.main 线程开始运行,分配栈帧内存

(stack=2,locals=4)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VSGdS7Qz-1640177014217)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211221234053044.png)]

6.执行引擎开始执行字节码

bipush 10

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

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

ldc 将一个 int 压入操作数栈

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vwvvWX00-1640177014218)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211221234138426.png)]

istore_1

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A2aOzg6o-1640177014220)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211221234228514.png)]

ldc #3

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

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

好的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jfPCAAdc-1640177014221)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211221234257680.png)]

istore_2

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pb17bgZ2-1640177014223)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211221234336050.png)
iload_1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FWEioCA7-1640177014224)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211221234352620.png)]

iload_2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vCqqVeF0-1640177014225)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211221234419995.png)]

在这里插入图片描述

iadd

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HO4NZXe6-1640177014226)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211221234436133.png)]

istore_3

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6x7CwJVu-1640177014226)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211221234457229.png)]

getstatic #4

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HaX7ohB8-1640177014227)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211221234514251.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tBzOV14F-1640177014228)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211221234524553.png)]

iload_3

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iX0S1kRK-1640177014229)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211221234542159.png)]

invokevirtual #5

找到常量池 #5 项

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cxyCCui9-1640177014230)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211221234605736.png)]

执行完毕,弹出栈帧

清除 main 操作数栈内容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HJXzBP18-1640177014231)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211221234621374.png)]

return

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

程序结束

5.条件判断指令

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XyD40r4E-1640177014235)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211221235655811.png)]

几点说明:

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

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

6.构造方法

()V:编译器会按从上至下的顺序,收集所有 static 静态代码块和静态成员赋值的代码,合并为一个特殊的方法 ()V :方法会在类加载的初始化阶段被调用

7.其他

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

dup 是赋值操作数栈栈顶的内容,本例即为【对象引用】,为什么需要两份引用呢,一个是要配

合 invokespecial 调用该对象的构造方法 “”😦)V (会消耗掉栈顶一个引用),另一个要

配合 astore_1 赋值给局部变量

最终方法(fifinal),私有方法(private),构造方法都是由 invokespecial 指令来调用,属于静

态绑定

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

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

比较有意思的是 d.test4(); 是通过【对象引用】调用一个静态方法,可以看到在调用

invokestatic 之前执行了 pop 指令,把【对象引用】从操作数栈弹掉了😂

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

8.finally相关面试题

finally出现了return

public class Demo1 {
    public static void main(String[] args) {
        int result=test();
        System.out.println(result);
    }

    private static int test() {
        try{
            return 10;
        } finally {
            return 20;
        }
    }
}
结果为20

finally对返回值影响

public class Demo1 {
    public static void main(String[] args) {
        int result=test();
        System.out.println(result);
    }

    private static int test() {
        int i = 10;
        try{
            return i;
        } finally {
            i = 20;
        }
    }
}
结果为10