深圳品牌网站设计推广/百度手机
字节码新手很容易被厚厚的 JVM 书籍劝退,即使我看过相关书籍,工作真正用到时也全忘了,还得现学。
等我有了一定的字节码阅读经验,才发现字节码其实非常简单,只需要三步就能快速学会:
- 先了解 JVM 的基本结构,只需要了解字节码相关的结构即可
- 然后将指令分成几类,学习每一类是用来操作 Jvm 哪个组件的
- 最后学习字节码的常见模式,比如 new 一般会被翻译成哪几指令,等等
之后按需查询 JVM指令官方文档,就可以出师了。
相比官方文档,本文更加适合顺序阅读,读完后再按需查阅官方文档。
JVM 的基本结构
只需要了解三个和字节码相关的结构。
栈帧
JVM 每调用一个方法就会压入一个新的栈帧到栈上,比如下面的例子:
public static void main() {a();
}public static void a() {b();
}public static void b() {//...此处的栈如图所示...
}
虽然例子中举的是静态方法,但是对象方法也是一样。
重点:栈帧代表一个 Java 方法,里面装的是 Java 方法的运行时数据
操作数栈
每个栈帧中会有一个操作数栈,这其实就是一个用来执行各种计算的工作区,比如加法的执行过程:
- 将 1 压栈
- 将 2 压栈
- 栈顶两个元素相加,将结果 3 压栈
局部变量表
栈帧中除了用于运算的操作数栈,还有用来临时存储变量的 “局部变量表”,比如下面的变量 a
:
public static void main() {// a 会被存入 “局部变量表”int a = 10;//...此处省略1w行代码...// 将 a 从局部变量表中取出int b = a + 1;
}
堆
操作数栈是栈帧中的存储,所以其生命周期仅限于一个 Java 方法。
而堆就是能够跨越 Java 方法的存储。一般用 new
新建的对象都是在堆里,然后在栈帧中只保留一个引用指针。
比如 Object a = new Object();
指令分类学习
我们可以根据操作的 JVM 组件,将指令分类。每一类我们只要认识常见的几个,剩下的都非常相似。
指令的结构
JVM 指令的结构和 linux 命令差不多:指令 参数1 参数2 ...
;其中每个部分占一个字节,所以叫做 “字节码”。
栈操作
- dup: 复制栈顶元素
- add: 将栈顶两个元素出栈相加,然后将结果入栈
JVM 并不存在
add
指令,而是各种专门数据类型的相加指令,比如iadd
是整型相加,dadd
是浮点类型相加等等,其他指令也是类似的规律
- ldc/bipush/const: 将常量压栈
ldc index
: 将常量池中第 index 个元素入栈bipush byte
:此时常量不在常量池中,而是作为一个 byte 被编码在了指令中- const 则是一系列指令,比如
iconst_0
表示将整型0
入栈,iconst_1
表示将整型1
入栈,dconst_0
表示将浮点型 0 入栈等等,以此类推。可见 const 相比前两个是更加压缩的表示,一个iconst_1
同时表达了指令(iconst
)和常量值(1
),这么做的好处是可以节约一个字节。
从
add
和const
指令中可以看出 JVM 指令会用前缀表示指令操作的数据类型,比如i
表示整型,d
表示浮点型等等
局部变量表操作
局部变量表是一个数组,通过下标访问。
store i
: 将栈顶的值存储到局部变量表的 i 位置load i
:将局部变量表 i 位置的值加载到栈顶
store 和 load 也都是一系列指令,比如
istore i
,iload i
,dstore i
,dload i
等等,甚至
虽然我们看到代码里的变量都是有具体的名字的,比如 int a = 1,b = 2
中的变量 a, b。其实在运行时,这些变量都会按照方法中出现的顺序被翻译成局部变量表的一个下标,int a = 1,b = 2
对应的字节码就是:
// 常量 1 入栈
0: iconst_1
// 将常量 1 存储到局部变量表的 1 位置(其实就是变量 a)
1: istore_1
// 常量 2 入栈
2: iconst_2
// 将常量 2 存储到局部变量表的 2 位置(其实就变量 b)
3: istore_2
如图所示:
局部变量表的 0 号位置一般是用来保存
this
引用的,所以图中没有使用。
堆操作
堆主要还是依靠保存在栈中的引用来操作的。
通过 new
指令在堆中新建一个对象,然后将引用保存在栈顶。
常见模式
静态方法调用
- 将参数入栈
- 然后使用
invokestatic
指令调用静态方法
如下代码:
static void a() {b(1,2,3);
}static void b(int a, int b, int c) {//...
}
此时 a 方法的字节码就是:
// 将参数入栈
0: iconst 1
1: iconst_2
2: iconst3
// 调用静态方法
3: invokestatic b:(III)V
成员方法调用
- 将调用对象入栈
- 将参数入栈
- 调用成员方法
如下代码:
class Test {void a() {b(1,2,3);}void b(int a, int b, int c) {}
}
此时 a 方法的字节码:
// 将调用对象入栈
// 回忆前文, this 引用存储在局部变量表的 0 号位置, 所以可以从局部变量表的 0 号位置加载到
0: aload_0
// 调用参数入栈
1: iconst_1
2: iconst_2
3: iconst_3
// 调用成员方法
4: invokevirtual b:(III)V
成员方法的调用,本质上是将对象作为隐藏的第 0 个参数,比如当调用 object.a(1)
时,在 JVM 层面相当于 a(object, 1)
。
另外,针对不同的情况,invoke
有一些 invokeXxx
方法:
invokeinterface
:调用接口方法invokevirtual
: 调用对象的成员方法,是最常见的invokespecial
: 调用构造器等特殊方法。比如下文中的构造函数就是使用这个指令调用的
构造对象
总体步骤如下:
- 用
new
指令新建一个空对象 - 用
dup
复制栈顶 - 像 “方法调用” 中一样将构造函数的参数逐个入栈
invokespecial <init>
调用对应的构造函数完成初始化
比如下面的代码:
Integer a = new Integer(10);
对应的指令就是:
// 步骤 1.
0: new java/lang/Integer
// 步骤 2.
3: dup
// 步骤 3.
4: bipush 10
// 步骤 4.
6: invokespecial java/lang/Integer."<init>":(I)V
前面编号代表的是指令位于第几个字节,从上面可以看出,除了 dup 以外,其他指令都需要占用多个字节
在编码阶段,对象看起来是构造好才返回的,但在字节码层面,其实对象新建和初始化是两个步骤,先新建一个空对象,然后才调用的初始化方法。
为什么需要一个 dup
,因为构造方法在 JVM 层面其实就是一个普通的成员方法, invokespecial
需要将对象和参数一起从栈中弹出,而构造方法执行完后还需要在栈上保留一个对象,因此需要 dup 保留一个副本。
分支判断
分支判断主要使用 if_xxx index
系列指令,如果满足条件就跳转到 index
位置的指令执行。
如下代码:
public void b(int a, int b) {int c;if (a > b) {c = 1;} else {c = 2;}return;
}
编译后的字节码是:
// 加载局部变量 a
0: iload_1
// 加载局部变量 b
1: iload_2
// 如果 a <= b, 则跳转到位置 10 执行(其实就是 else 分支的位置)
2: if_icmple 10
5: iconst_1
6: istore_3
// 跳转到位置 12 执行(其实就是跳过 else 分支的指令)
7: goto 12
10: iconst_2
11: istore_3
12: return
大体逻辑如图:
做字节码实验时的注意点
当我们编写好测试的 Java 代码 Test.java
,可以用下面两条命令查看它的字节码:
javac Test.java
javap -c -v -l Test.class
需要注意的是,javac
中会做一些优化,导致字节码和源码对不上,比如下面的代码:
public static void main(String[] args) {if (2 < 1) {int a = 2;} else {int a = 1;}
}
编译后字节码是:
0: iconst_1
1: istore_1
2: return
可以看出,字节码其实只编译了 int a = 1
这一行代码,没有编译 if ... else ...
逻辑。这是因为 2 < 1
肯定是 false,于是编译器就把这段逻辑优化掉了。
如何阅读字节码官方文档
这篇文章只是带大家入个门,重点还是要能在遇到不认识的字节码时自主查阅 Java字节码官方文档。
比如下面的 iadd
字节码
- Operation:可看出是做 int 型加法操作
- Operand Stack:表示指令执行会导致栈变化,
iadd
会将栈顶两个元素出栈,然后将结果入栈
End
作者:元青
微信公众号 「技乐书香」
相关文章:

五分钟看懂Java字节码:极简手册
字节码新手很容易被厚厚的 JVM 书籍劝退,即使我看过相关书籍,工作真正用到时也全忘了,还得现学。 等我有了一定的字节码阅读经验,才发现字节码其实非常简单,只需要三步就能快速学会: 先了解 JVM 的基本结…...

C++ 类与对象(下)
✅<1>主页:我的代码爱吃辣 📃<2>知识讲解:C 🔥<3>创作者:我的代码爱吃辣 ☂️<4>开发环境:Visual Studio 2022 💬<5>前言:C类与对象的收尾工作&#…...

Java基础——I/O
一、异常 异常是程序中可能出现的问题,它的父类是Exception。异常分为两类,编译时异常、运行时异常。 编译时异常:没有继承RuntimeException的异常,直接继承于Exception。编译阶段就会错误提示。运行时异常:RuntimeE…...

关于@hide的理解
在上一篇文章《学习HandlerThread》我们提到虽然HandlerThread类里有getThreadHandler()方法得到Handler,但是我们不可能调用到它。因为这个方法用hide注释了 /*** return a shared {link Handler} associated with this thread* hide*/NonNullpublic Handler getT…...

使用python加密主机文件几种方法实现
本文主要介绍了使用python加密主机文件几种方法实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧数据加密是一种保护数据安全的技术,通过对数据进行编…...

西湖论剑 2023 比赛复现
WEB real_ez_node 在 route/index.js 中: router.post(/copy,(req,res)>{res.setHeader(Content-type,text/html;charsetutf-8)var ip req.connection.remoteAddress;console.log(ip);var obj {msg: ,}if (!ip.includes(127.0.0.1)) {obj.msg"only for…...

微信小程序更换管理员/重置管理员
方式1: 首先进入微信公众平台官网进入并登录后在管理中找到成员管理选项找到管理员点击后方的修改选项需要使用原管理员的微信进行扫码验证扫码后在手机上确认绑定新管理员,注意:如果是个人账号不可以更改成其他人。 方式2:原管…...

企业进存销管理系统
技术:Java、JSP等摘要:随着当今世界计算机技术的飞速发展,计算机在企业管理中应用的普及,利用计算机实现企业进销存管理势在必行。本系统结合公司实际的进销存制度,通过对本公司的供应商、客户、商品、进货、销售、进销…...

C++入门
变量变量创建的语法: 数据类型 变量名 变量初始值;int a 10;cout << a << endl;常量作用:用于记录程序中不可更改的教国C定义常量两种方式1).#define 宏常量:#define 常量名 常量值通常在文件上方定义。表示一个常量2).const 修饰的变量const 数据类型 常量名 常…...

视频知识点(20)- H264码流如何在SPS中获取宽高信息?
《音视频开发》系列-总览 前沿 了解H264视频编码格式的小伙伴都知道,H264编码中存在两个非常重要的参数集。没错,它们就是序列参数集(SPS)和图像参数集(PPS),而且通常情况下,PPS会依赖SPS中的部分参数信息,同时,视频码流的宽高信息也存储在SPS中。那么如何从中获取视…...

鲜花数据集实验结果总结
从read_split_data中得到:训练数据集,验证数据集,训练标签,验证标签。的所有的具体详细路径 数据集位置:https://download.csdn.net/download/guoguozgw/87437634 import os #一种轻量级的数据交换格式, …...

ElasticJob-Lite架构篇 - 认知分布式任务调度ElasticJob-Lite
前言 本文基于 ElasticJob-Lite 3.x 版本展开分析。 如果 Quartz 集群中有多个服务端节点,任务决定在哪个服务端节点上执行的呢? Quartz 采用随机负载,通过 DB 抢占下一个即将触发的 Trigger 绑定的任务的执行权限。 在 Quartz 的基础上&…...

【直击招聘C++】2.6 对象之间的复制
2.6 对象之间的复制一、要点归纳1. 对象之间的复制操作1.1 运算符1.2 拷贝构造函数2. 对象之间的浅复制和深复制2.1 对象的浅复制2.2 对象的深复制二、面试真题解析面试题1面试题2一、要点归纳 1. 对象之间的复制操作 同一个类的对象之间可以进行复制操作,即将一个…...

学了这么久python,不会连自己啥python版本都不知道吧?
人生苦短,我用Python 源码资料电子书:点击此处跳转文末名片获取 查看 Python 版本 我们可以在命令窗口(Windows 使用 winR 调出 cmd 运行框)使用以下命令查看我们使用的 Python 版本: python -V 或 python --version 以上命令执行结果如下: …...

Revive:从间谍软件进化成银行木马
2022 年 6 月,Cleafy 研究人员发现了一个新的安卓银行木马 Revive。之所以选择 Revive 这个名称,是因为恶意软件为防止停止工作启用的一项功能名为 revive。 Revive 属于持续潜伏的那一类恶意软件,因为它是为特定目标开发和定制的。这种类型…...

Python 之 NumPy 简介和创建数组
文章目录一、NumPy 简介1. 为什么要使用 NumPy2. NumPy 数据类型3. NumPy 数组属性4. NumPy 的 ndarray 对象二、numpy.array() 创建数组1. 基础理论2. 基础操作演示3. numpy.array() 参数详解三、numpy.arange() 生成区间数组四、numpy.linspace() 创建等差数列五、numpy.logs…...

与六年测试工程师促膝长谈,他分享的这些让我对软件测试工作有了全新的认知~
不知不觉已经从事软件测试六年了,2016年毕业到进入外包公司外包给微软做软件测试, 到现在加入著名的外企。六年的时间过得真快。长期的测试工作也让我对软件测试有了比较深入的认识。但是我至今还是一个底层的测试人员,我的看法都比较狭隘&am…...

裕太微在科创板上市:市值约186亿元,哈勃科技和小米基金为股东
2月10日,裕太微电子股份有限公司(下称“裕太微”,SH:688515)在上海证券交易所上市。本次上市,裕太微的发行价为92元/股,发行2000万股,发行市盈率不适用,发行后总股本8000万股。 根据…...

毕业后5年,我终于变成了月薪13000的软件测试工程师
我用了近2个月的时间转行,在今年1月底顺利入职了一家北京的互联网公司,从事的是软件测试的工作。 和大家看到的一样,我求职的时间花费的比较短,求职过程非常顺利,面试了一周就拿到了3家offer,3家offer的薪…...

实践指南|如何在 Jina 中使用 OpenTelemetry 进行应用程序的监控和跟踪
随着软件和云技术的普及,越来越多的企业开始采用微服务架构、容器化、多云部署和持续部署模式,这增加了因系统失败而给运维/ SRE / DevOps 团队带来的压力,从而增加了开发团队和他们之间的摩擦,因为开发团队总是想尽快部署新功能&…...

MySQL 创建数据表
在创建数据库之后,接下来就要在数据库中创建数据表。所谓创建数据表,指的是在已经创建的数据库中建立新表。 创建数据表的过程是规定数据列的属性的过程,同时也是实施数据完整性(包括实体完整性、引用完整性和域完整性)…...

一文详解网络安全事件的防护与响应
网络安全事件的发生,往往意味着一家企业的生产经营活动受到影响,甚至数据资产遭到泄露。日益复杂的威胁形势使现代企业面临更大的网络安全风险。因此,企业必须提前准备好响应网络安全事件的措施,并制定流程清晰、目标明确的事件响…...

vue directive 注册局部指令
注册局部指令 vue directive 在注册局部指令时,是通过在组件 options 选项中设置 directives 属性。如下: directives: {focus: {// 指令的定义inserted: function (el) {el.focus()}} }在模板中的任何元素上都可以使用新的 v-focus propertyÿ…...

LC-70-爬楼梯
原题链接:爬楼梯 个人解法 思路: 动态规划 状态表示:f[i]表示走到第n阶台阶有几种方法 状态转移:f[i] f[i -1] f[i - 2] 这实际上就是斐波那契数列,通过转移可以看到,我们只用了三个变量,故…...

Scratch少儿编程案例-可爱的简约贪吃蛇
专栏分享 点击跳转=>Unity3D特效百例点击跳转=>案例项目实战源码点击跳转=>游戏脚本-辅助自动化点击跳转=>Android控件全解手册点击跳转=>Scratch编程案例👉关于作者...

编译 Android 时如何指定输出目录?
文章目录0. 导读1. 指定 Android 编译输出目录2. 指定 Android dist 编译输出目录3. 指定 Android 模块编译输出目录4. Android 源码中编译相关的文档0. 导读 偶尔会有朋友问编译 Android 时如何指定输出目录? 这里有两种情况: 一是如何将 Android 默认的输出目…...

CF1574C Slay the Dragon 题解
CF1574C Slay the Dragon 题解题目链接字面描述题面翻译题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1提示代码实现题目 链接 https://www.luogu.com.cn/problem/CF1574C 字面描述 题面翻译 给定长度为 nnn 的序列 aaa,mmm 次询问,每次询…...

创建Django项目
创建Django项目 步骤 创建Django项目 django-admin startproject name 创建子应用 python manager.py startapp name创建工程 在使用Flask框架时,项目工程目录的组织与创建是需要我们自己手动创建完成的。 在django中,项目工程目录可以借助django提供…...

CUDA中的统一内存
文章目录1. Unified Memory Introduction1.1. System Requirements1.2. Simplifying GPU Programming1.3. Data Migration and Coherency1.4. GPU Memory Oversubscription1.5. Multi-GPU1.6. System Allocator1.7. Hardware Coherency1.8. Access Counters2. Programming Mode…...

利用机器学习(mediapipe)进行人脸468点的3D坐标检测--视频实时检测
上期文章,我们分享了人脸468点的3D坐标检测的图片检测代码实现过程,我们我们介绍一下如何在实时视频中,进行人脸468点的坐标检测。 import cv2 import mediapipe as mp mp_drawing = mp.solutions.drawing_utils mp_face_mesh = mp.solutions.face_mesh face_mesh = mp_fac…...