三言两语:JVM 字节码执行实例分析

2024-04-10 13:18

本文主要是介绍三言两语:JVM 字节码执行实例分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言
最近在看《Java 虚拟机规范》和《深入理解JVM虚拟机》,对于字节码的执行有了进一步的了解。字节码就像是汇编语言,是 JVM 的指令集。下面我们先对 JVM 执行引擎做一下简单介绍,然后根据实例分析 JVM 字节码的执行过程。包括:

for 循环字节码分析
try-catch-finally 字节码分析
运行时栈帧结构
栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧存储了方法的局部变量表,操作数栈,动态连接和方法返回地址等信息。每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。

在编译程序员代码的时候,栈帧中局部变量表和操作数栈的大小已经确定了,并且写入到方法表中的Code属性中。

在活动线程中,只有位于栈顶的栈帧才是有效的, 称为当前栈帧,与这个栈帧关联的方法称为当前方法。执行引擎运行的所有字节码指令只对当前栈帧进行操作。

局部变量表
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。局部变量表的容量以变量槽(slot)为最小单位,每个 slot 保证能放下 32 位内的数据类型。虚拟机通过索引定位的方式使用局部变量表,索引值从 0 开始。值得注意的是,对于实例方法,局部变量表中第 0 位索引的 slot 默认是this引用;静态方法则不是。而且为了节约内存,slot 是可以重用的。

操作数栈
操作数栈的元素可以是任意的 Java 数据类型。当一个方法开始时,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈入栈操作。

实例分析
下面分析的字节码指令主要是对局部变量表和操作栈的读写。

for 循环字节码分析
void spin() {
int i;
for (i = 0; i < 100; i++) {
; // Loop body is empty
}
}
上面是一个空循环的代码,编译后的字节码如下:

Method void spin()
0 iconst_0 // Push int constant 0
1 istore_1 // Store into local variable 1 (i=0)
2 goto 8 // First time through don’t increment
5 iinc 1 1 // Increment local variable 1 by 1 (i++)
8 iload_1 // Push local variable 1 (i)
9 bipush 100 // Push int constant 100
11 if_icmplt 5 // Compare and loop if less than (i < 100)
14 return // Return void when done
相信大家看到上面的代码都是一脸懵逼,即使有注释还是不知道字节码到底做了什么操作。下面我就图解每一条指令,帮助理解。上面的代码都是对局部变量表和操作数栈的操作,所以我们的关注点就在这两个区域上。(栈是自顶向下的)

0 iconst_0 //把常量0放入栈
±-------±-------+
| local | stack |
±----------------+
| | 0 |
±----------------+
| | |
±-------±-------+

1 istore_1 //把栈顶的元素出栈,存到局部变量表索引为1的位置
±-------±-------+
| local | stack |
±----------------+
| 0 | |
±----------------+
| | |
±-------±-------+

2 goto 8 //跳转到第8条指令

8 iload_1 //把局部变量表中索引为1的变量入栈
±-------±-------+
| local | stack |
±----------------+
| 0 | 0 |
±----------------+
| | |
±-------±-------+

9 bipush 100 //把100入栈
±-------±-------+
| local | stack |
±----------------+
| 0 | 0 |
±----------------+
| | 100 |
±-------±-------+

11 if_icmplt 5 //出栈两个元素v1,v2,比较它们的值,当且仅当v1 < v2,跳转到指令5
±-------±-------+
| local | stack |
±----------------+
| 0 | |
±----------------+
| | |
±-------±-------+

5 iinc 1 1 //自增局部变量表中索引为1的值
±-------±-------+
| local | stack |
±----------------+
| 1 | |
±----------------+
| | |
±-------±-------+

//进行下次循环直到指令11不满足,到达指令14
14 return //清空栈,执行引擎把控制权交换给调用者。
±-------±-------+
| local | stack |
±----------------+
| 100 | |
±----------------+
| | |
±-------±-------+
以上就是for循环字节码执行的过程。可以发现,所有指令都是围绕者局部变量表和操作数栈在操作。

解惑
指令iconst_0,iload_1的命名解读
第一个i代表这是对int数据类型进行的操作
const,load是操作码
0,1是隐含的操作数
上面的两个指令等价于iconst 0,iload 1
详细的字节码解释查阅《JVM 虚拟机规范》

try-catch-finally 字节码分析
static int inc(){
int x;
try {
x = 1;
return x;
} catch (Exception e){
x = 2;
return x;
} finally {
x = 3;
}
}
下面是它的字节码,这次我就不画图了,里面的命令跟上面的类似。

static int inc();
descriptor: ()I
flags: ACC_STATIC
Code:
stack=1, locals=4, args_size=0
0: iconst_1 //try 块中的 x = 1;
1: istore_0 //保存栈顶元素到局部变量表中索引为 0 的 slot 中
2: iload_0 //加载局部变量表中索引为 0 的值到栈中
3: istore_1 //保存栈顶元素到局部变量表中索引为 1 的 slot 中
4: iconst_3 //finally 块中的 x = 3;
5: istore_0 //保存栈顶元素到局部变量表中索引为 0 的 slot 中,x 的值存在这里。
6: iload_1 //加载局部变量表中索引为 1 的值到栈中
7: ireturn //返回栈顶元素,即 x = 1;正常情况下函数运行到这里就结束了,如果出现异常根据异常表跳转到指定的位置
8: astore_1 //给 catch 块中定义的 Exception e 赋值,存储在 slot1 中。
9: iconst_2 //catch 块中的 x = 2;
10: istore_0
11: iload_0
12: istore_2
13: iconst_3 //finally 块中的 x = 3;
14: istore_0
15: iload_2
16: ireturn //此时返回的是 slot2 中的值,即 x = 2
17: astore_3 //如果出现不属于 java.lang.Exception 及其子类的异常,才会根据异常表中的规则跳转到这里。
18: iconst_3 //finally 块中的 x = 3;
19: istore_0
20: aload_3 //将异常加载到栈顶,
21: athrow //抛出栈顶的异常
Exception table:
from to target type
0 4 8 Class java/lang/Exception
0 4 17 any
8 13 17 any
字节码中 0 ~ 4 行将整数 1 赋值为变量 x,x 存储在 slot0 中,并且将 x 的值拷贝一份放到 slot1。如果没有出现异常,继续走到 5 ~ 7 行,将 x 赋值为 3,然后读取 slot1 中的值到栈顶,最后ireturn返回栈顶的值,方法结束。
如果出现异常,PC 寄存器指针转到第 8 行,第 8 ~ 16 行所做的事情就是将 2 赋值给 x,然后保存 x 的拷贝,最后将 x 赋值为 3。方法返回前将 x 的拷贝 2 读取到栈顶。
如果在 0 ~ 4,8 ~ 13 行中出现其他异常,则跳转到第 17 行执行,先同样执行finally块中的x = 3,最后抛出异常,方法结束。
可以看到,Java 字节码是通过异常表的方式来决定代码执行的路径。而finally的实现是通过在每个路径的最后加入finally块中的字节码实现的。
深圳网站建设www.sz886.com

这篇关于三言两语:JVM 字节码执行实例分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/891170

相关文章

Java的栈与队列实现代码解析

《Java的栈与队列实现代码解析》栈是常见的线性数据结构,栈的特点是以先进后出的形式,后进先出,先进后出,分为栈底和栈顶,栈应用于内存的分配,表达式求值,存储临时的数据和方法的调用等,本文给大家介绍J... 目录栈的概念(Stack)栈的实现代码队列(Queue)模拟实现队列(双链表实现)循环队列(循环数组

Java中Switch Case多个条件处理方法举例

《Java中SwitchCase多个条件处理方法举例》Java中switch语句用于根据变量值执行不同代码块,适用于多个条件的处理,:本文主要介绍Java中SwitchCase多个条件处理的相... 目录前言基本语法处理多个条件示例1:合并相同代码的多个case示例2:通过字符串合并多个case进阶用法使用

Java中的Lambda表达式及其应用小结

《Java中的Lambda表达式及其应用小结》Java中的Lambda表达式是一项极具创新性的特性,它使得Java代码更加简洁和高效,尤其是在集合操作和并行处理方面,:本文主要介绍Java中的La... 目录前言1. 什么是Lambda表达式?2. Lambda表达式的基本语法例子1:最简单的Lambda表

Java中Scanner的用法示例小结

《Java中Scanner的用法示例小结》有时候我们在编写代码的时候可能会使用输入和输出,那Java也有自己的输入和输出,今天我们来探究一下,对JavaScanner用法相关知识感兴趣的朋友一起看看吧... 目录前言一 输出二 输入Scanner的使用多组输入三 综合练习:猜数字游戏猜数字前言有时候我们在

Spring Security+JWT如何实现前后端分离权限控制

《SpringSecurity+JWT如何实现前后端分离权限控制》本篇将手把手教你用SpringSecurity+JWT搭建一套完整的登录认证与权限控制体系,具有很好的参考价值,希望对大家... 目录Spring Security+JWT实现前后端分离权限控制实战一、为什么要用 JWT?二、JWT 基本结构

java解析jwt中的payload的用法

《java解析jwt中的payload的用法》:本文主要介绍java解析jwt中的payload的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java解析jwt中的payload1. 使用 jjwt 库步骤 1:添加依赖步骤 2:解析 JWT2. 使用 N

springboot项目如何开启https服务

《springboot项目如何开启https服务》:本文主要介绍springboot项目如何开启https服务方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录springboot项目开启https服务1. 生成SSL证书密钥库使用keytool生成自签名证书将

Java实现优雅日期处理的方案详解

《Java实现优雅日期处理的方案详解》在我们的日常工作中,需要经常处理各种格式,各种类似的的日期或者时间,下面我们就来看看如何使用java处理这样的日期问题吧,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言一、日期的坑1.1 日期格式化陷阱1.2 时区转换二、优雅方案的进阶之路2.1 线程安全重构2

Java中的JSONObject详解

《Java中的JSONObject详解》:本文主要介绍Java中的JSONObject详解,需要的朋友可以参考下... Java中的jsONObject详解一、引言在Java开发中,处理JSON数据是一种常见的需求。JSONObject是处理JSON对象的一个非常有用的类,它提供了一系列的API来操作J

SpringBoot多数据源配置完整指南

《SpringBoot多数据源配置完整指南》在复杂的企业应用中,经常需要连接多个数据库,SpringBoot提供了灵活的多数据源配置方式,以下是详细的实现方案,需要的朋友可以参考下... 目录一、基础多数据源配置1. 添加依赖2. 配置多个数据源3. 配置数据源Bean二、JPA多数据源配置1. 配置主数据