实战字节码-01-基础知识

news2025/1/18 7:24:12

开篇

字节码是什么、做什么这类问题不在这里赘述,《实战字节码》系列旨在帮助没接触过字节码的人能够快速上手做应用开发,并构建字节码技术的知识骨架,所以不会系统地介绍字节码技术的方方面面,也尽量避免叙述理论和概念相关的东西。重点在于动手,手把手做一个完整的小工程:以一个Maven插件的形式触发编译时字节码扫描和修改。

当然,在上手coding之前,有些基础知识还是要了解的,至少对字节码运行方式、核心指令有个印象。不必担心一篇能否涵盖所有基础知识,可以非常肯定的说——不可能。但是,以我个人开发经验而言,了解过本篇涉及的基础知识后,上手做点东西是没问题的。

没有实践只能获得概念,一边实践一边学习得到的才是知识和技能。所以后面用到哪学到哪,也不迟。

玩转字节码

简单:字节码开发并不需要太多储备知识,虽然总共有200多种指令,但其实主要就分为存取(load/store)、运算(add)、跳转(jump)、调用(invoke)、常量(ldc)、异常(try-catch-finally)这几大类,每种类型理解一个就可以以此类推其他的,比如加载int类型用iload指令,加载对象类型用aload指令,大部分指令都是不同类型的重载而已。

模仿:最快速的上手方法就是模仿。先把想要的字节码用Java语言写出来,再用javac编译成class,通过ASMPlugin、jclasslib等工具查看编译好的class文件就可以看到你想要的字节码。

基础知识

JVM运行字节码

JVM是基于栈技术实现的虚拟机,源代码被编译成字节码指令后,所有指令都按照入栈、出栈的方式执行,栈顶永远是当前正在执行的指令。而运行时变量在栈中存储的位置和大小在编译时就已经确定了,这就是本地变量表的作用(LocalVariableTable)。

举个栗子,来看看int sum = 6 + 9语句在JVM栈中是怎么执行的。如下图所示:

  1. 从本地变量表的第1和第2个位置依次将int类型的值压入栈顶,分别对应6和9两个值,此时栈状态如[上-左图]。load有一系列变种,这个例子中iload表示load操作的是int类型。字节码中还有很多指令跟类型绑定,在此不一一列举,可以此类推。

  1. 用iadd指令对栈顶两个int数值(6、9)做加法运算,如[上-中图]所示。

  1. iadd指令执行后把栈顶6、9这两个值出栈,并将运算结果(15)压入栈顶,如[上-右图]所示。

  1. 接下来要将运算结果存到本地变量表中以供其他指令使用,这里是int类型的store指令istore,这个指令将下放int值(15)存入本地变量表第3个位置,并将该值出栈,如[下-右图]所示。

  1. 此时栈中为空,如[下-中图]所示。

  1. 使用iload指令将本地变量表中第3个int值加载到栈顶,即完成sum = 15这步赋值操作。如[下-左图]所示。

字节码长什么样

上图是Java源码,我们可以通过javap命令或其他工具可以看到编译后的class文件中fun1方法对应的字节码数据结构,如下图

很多书上或教程上都长篇累牍地介绍字节码二进制数据格式,乏味而且用处不大,通常,如果你不想自己写字节码解析器的话,大可不必了解那些规则繁琐的数据结构。我们面向实战应用,跳过这些,直接通过javap先看看解析后的字节码数据结构是什么样的。实际开发中,使用ASM、Javassist等工具即可直接拿到解析后的字节码数据。

其中,比较重要的是画红框的三部分:

  1. 方法(Method)声明部分,descriptor是对fun1方法参数和返回值的定义,对应fun1中的参数和返回值。I表示int类型。L开头的表示对象,List是实际的类型,对象类型用分号;结束。圆括号后表示返回值类型,V表示void类型。descriptor是字节码中无处不在的类型描述符,无论是识别Class、Method、Field都需要它,重要性可想而知,下文会具体介绍。

  1. 字节码的核心,方法中调用指令都在Code中。而第一行的三个参数也很重要,stack表示方法中调用栈最大深度、locals表示局部变量最多占用的槽数(存储局部变量用,除long、double两类型占用双槽外,其他类型变量均占一个槽)、args_size表示方法入参个数,fun1方法中有两个参数,分别是int和List类型,而成员方法在编译时会在第0个位置自动加入this引用,所以这里是3个参数。

  1. 局部变量表对应上面的locals和args_size,这部分详细开列了方法内局部变量的信息,可以看到第一个就是编译器自动注入的this引用。局部变量表之所以重要,是因为修改或创建字节码时,这部分可能需要自己计算,如果计算有误,则运行时会崩溃。当然,有的工具如ASM提供了自动计算stack和locals的功能。

字节码类型描述符

官方文档(https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.2

字节码类型

对应Java类型

B

byte

C

char

D

double

F

float

I

int

J

long

LClassName;

Object和各级子类

S

short

Z

boolean

[

Array

V

void

上表中非加粗类型都是实际类型的首字母,而加粗类型则需要特别注意,它们并非使用首字母。

示例

Java类型

字节码类型

byte[]

[B

long[][]

[[J

Boolean[]

[Ljava/lang/Boolean;

重要指令

字节码中有200多种指令,大部分指令可以通过名字猜到用途。初学者重点关注几大类指令即可。

加载和存储

字节码中一般通过加载(load系列)指令将变量加载到栈上,如果需要保存到局部变量表中,则通过存储(store系列)指令进行操作。而常量加载使用const、push、ldc系列指令。

类型

字节码指令

说明

加载变量

iload_<n>, lload_<n>, fload_<n>, dload_<n>, aload_<n>

加载变量到栈上,下划线后是变量在局部变量表中的位置,如:iload_3就是从局部变量表下标为3的位置处加载int类型变量

存储变量

istore_<n>, lstore_<n>, fstore_<n>, dstore_<n>, astore_<n>

与load指令对应,将变量存储到局部变量表中,下划线后是局部变量表的位置。

相对于变量加载,常量加载要更复杂一些,取值范围不同,对应的指令也不同,具体见下表

字节码指令

说明

aconst_null

加载null

iconst_m1

加载int类型的-1

iconst_<i>

加载int类型的值i,i的范围是1-5。如加载int类型的3位iconst_3

lconst_<l>

加载long类型的值0、1。

fconst_<f>

加载float类型的值0、1、2。

dconst_<d>

加载double类型的值0、1。

bipush

加载int类型-128 ~ 127的值

sipush

加载int类型-32768 ~ 32767的值

ldc

加载int、float、String常量,ldc只能在常量池中寻找1个字节范围内的索引值,上限为255。

ldc_w

与ldc相同,但是可以在常量池中寻找2个字节范围内的索引值,可以覆盖完整的常量池。

ldc2_w

加载long、double常量。

条件指令

对应Java关键词

字节码指令

说明

if

ifeq, ifne, iflt, ifle, ifgt, ifge, ifnull, ifnonnull, if_icmpeq, if_icmpne, if_icmplt, if_icmple, if_icmpgt if_icmpge, if_acmpeq, if_acmpne

Java中的if指令在不同场景下对应多种字节码指令,名称相近的指令作用相同,只不过操作不同类型数据而已。例如i系列的指令比较int类型数据,a系列指令比较引用类型(对象)数据。

switch

tableswitch, lookupswitch

Java中switch关键词比较数据时,如果case值比较紧凑,则使用tableswitch指令,将case合并成数组,通过下标直接访问,时间复杂度是1;如果case值比较稀疏,则使用lookupswitch遍历查找,时间复杂度是n。

if, switch, break, continue, goto, try-catch-finally

goto, goto_w, jsr, jsr_w, ret

Java中的goto关键词虽然极少使用,但在字节码中,各种需要跳转的指令都依赖它来完成。比如,if语句块结束时大部分情况是通过goto来完成跳转的。

方法调用

字节码指令

说明

invokevirtual

Java中最常规的实例方法调用

invokeinterface

调用接口方法

invokespecial

构造方法(<init>)调用、private的实例方法调用、super的方法调用

invokestatic

调用静态方法

invokedynamic

Java7中新增的动态方法调用,Java中动态语言特性主要依赖这个指令实现,Java8中的Lambda就是通过该指令实现

上面这几种指令熟悉后,应该说程序的框架就能看懂了,还有一些像算术运算、try-catch-finally等指令,需要用到的时候通过文档或工具再查不晚,这里就不一一列举了。

必会总结

  • JVM栈运行方式,所有代码以入栈出栈方式运行

  • 字节码类型描述,要能识别和手写字节码中的各种类型

  • 各种加载、存储、条件、跳转、方法调用指令,要能理解它的作用,这点必不可少,但不必一定会手写

知道这些,下一篇就足以借助工具做字节码开发啦,嗯,就这么简单!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/144820.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【笔记:模拟CMOS集成电路】噪声——基本电路噪声性能(2)

【笔记&#xff1a;模拟CMOS集成电路】噪声——基本电路噪声性能&#xff08;2&#xff09;前言1 噪声——分析基础2 噪声——基本电路噪声性能2.1 MOS管噪声模型(1)电阻RG热噪声和沟道热噪声(2)衬底电阻热噪声(3)源极寄生电阻RS热噪声2.2常见组态的单级放大器噪声分析2.2.1 CS…

Python电影观众数量回归分析 随机森林 可视化 实验报告

实验报告&#xff1a;Python电影观众数量回归分析随机森林可视化-数据挖掘文档类资源-CSDN文库 前言 随着经济的发展和人民日益增长的美好生活需要的显著提升&#xff0c;看电影成为了人民群众在闲暇时光娱乐的重要途径。面对百花齐放的电影产业&#xff0c;哪些电影更能带动市…

OpenGL期末大作业——模拟太阳系(免费开源)

目录 一、项目介绍 二、配置与运行 三、项目地址 一、项目介绍 这是一个综合的openGL场景&#xff0c;模拟太阳系。场景中有光照&#xff0c;纹理等&#xff0c;并有丰富的视角控制&#xff0c;UI交互&#xff0c;比如WASD/IJKL键控制视角的移动等等。一个太阳系的场景&#…

大数据基础平台搭建-(五)Hive搭建

大数据基础平台搭建-&#xff08;五&#xff09;Hive搭建 大数据平台系列文章&#xff1a; 1、大数据基础平台搭建-&#xff08;一&#xff09;基础环境准备 2、大数据基础平台搭建-&#xff08;二&#xff09;Hadoop集群搭建 3、大数据基础平台搭建-&#xff08;三&#xff09…

Android今日头条平台隐私合规整改

头条应用管理平台开发者合规指引&#xff1a;https://open.oceanengine.com/labels/7/docs/1730079845340164头条审核合规的app&#xff0c;需要具备以下条件&#xff1a;用户协议弹窗抖音隐私政策&#xff08;模板示例&#xff09;&#xff1a;https://sf3-cdn-tos.douyinstat…

别告诉我你只知道waitnotify,不知道parkunpark???

目录 park&unpark wait,notify 和 park,unpark的区别 park unpark 原理 先调用park的情况 先调用park,在调用unpark的情况 先调用unpark,在调用park的情况 park&unpark park和unpark都是LockSupport的方法,park用于暂停当前线程的运行,而unpark用于恢复该线程的…

服务机器人“众生相”

在多种因素的共同作用下&#xff0c;早年间经常出现在科幻片中的机器人已然穿越荧屏来到了现实世界&#xff0c;为人们的日常生活增添了几分便利。比如&#xff0c;在家庭场景中&#xff0c;扫地机器人帮助人们解放双手&#xff1b;在餐饮场景中&#xff0c;送餐机器人为顾客提…

C语言--探索函数栈帧的创建与销毁

目录 为main函数开辟栈帧 创建变量 传参 为自定义函数开辟栈帧 返回 局部变量是怎么创建的&#xff1f;为什么局部变量的值是随机值&#xff1f;函数是怎么传参的&#xff1f;形参与实参的关系&#xff1f;函数怎么调用与返回&#xff1f; 我们用VS2013的环境进行探索…

Https为什么比Http安全?

Https是在Http之上做了一层加密和认证&#xff1b; 主要的区别是Https在TLS层对常规的Http请求和响应进行加密&#xff0c;同时对这些请求和响应进行数字签名。 Http请求的样式&#xff1a; 明文传输&#xff0c;通过抓包工具可以抓到 GET /hello.txt HTTP/1.1 User-Agent: c…

【三】Netty 解决粘包和拆包问题

netty 解决粘包和拆包问题TCP 粘包/拆包的基础知识粘包和拆包的问题说明TCP粘包/拆包 原因粘包和拆包的解决策略tcp 粘包/拆包 的问题案例大致流程如图:代码展示(jdk1.7)TimeServer 服务端启动类TimeServerHandler 服务端业务处理类TimeClient 客户端启动类TimeClientHandler 客…

Python入门注释和变量(2)

1.1输入 a input("请输入内容") print("您输入的内容是&#xff1a;{}".format(a)) 输入的内容会帮我们转换成字符串形式 2.1运算符 2.1.1算数运算符 以a 10 , b 20 为例进行运算 运算符描述实例加两个对象相加ab输出结果30-减得到负数或是一个数减…

You辉编程_有关boot

一、SpringBoot多环境配置 1.环境的配置信息 (1)application.properties #指定默认使用dev的配置 spring.profiles.activedev (2)application-dev.properties #开发环境 server.port8080 branchdev (3)application-prod.properties #测试环境 server.port8081 branchtest2…

【Nacos】Nacos介绍和简单使用

Nacos介绍及简单使用 Nacos介绍 Nacos是SpringCloudAlibaba架构中最重要的组件。Nacos是一个更易于帮助构建云原生应用的动态服务发现、配置和服务管理平台&#xff0c;提供了注册中心、配置中心和动态DNS服务三大功能。能够无缝对接SpringCloud、Spring、Dubbo等流行框架。 …

环境搭建 | MuMu模拟器 - Window10/11 系列

&#x1f5a5;️ 环境搭建 专栏&#xff1a;MuMu模拟器 - Window10/11 系列 &#x1f9d1;‍&#x1f4bc; 个人简介&#xff1a;一个不甘平庸的平凡人&#x1f36c; ✨ 个人主页&#xff1a;CoderHing的个人主页 &#x1f340; 格言: ☀️ 路漫漫其修远兮,吾将上下而求索☀️ …

FLV格式分析

1.FLV封装格式简介 FLV(Flash Video)是Adobe公司推出的⼀种流媒体格式&#xff0c;由于其封装后的⾳视频⽂件体积小、封装简单等特点&#xff0c;⾮常适合于互联⽹上使⽤。⽬前主流的视频⽹站基本都⽀持FLV。采⽤ FLV格式封装的⽂件后缀为.flv。 2.FLV封装格式分析 FLV封装格…

视频监视计划和设计软件丨IP Video System Design Tool功能简介

本软件提供快速轻松地设计现代视频监视系统之新方式 产品功能 • 降低寻找更好的视频摄像机位置的成本时&#xff0c;增加您的安全系统效能。 • 极短时间内&#xff0c;即可计算出精确的摄像机镜头焦距长度与视角。 • 使用2D和3D建模&#xff0c;检查每台摄像机的视野并寻…

新应用——合同管理应用,实现合同无纸化管理

合同管理应用&#xff0c;是建立在低代码技术基础上&#xff0c;结合企业的管理方式&#xff0c;为企业提供决策、计划、控制与经营绩效评估的全方位、系统化的合同管理解决方案。百数合同管理系统应用提供了从合同模板、合同签订、合同收付款和合同发票管理、合同归档&#xf…

我是如何两个月通过软件设计师的!

软设刚过&#xff0c;分享下经验 个人感觉不是很难&#xff0c;我都不好意思说我没怎么复习&#xff0c;本来以后自己要二战了&#xff0c;没想到&#xff0c;成绩还挺惊喜&#xff0c;大概是因为最后几天冲刺到点子上了。 攻略&#xff1a; 搜集资料&#xff0c;搜集考试相…

一、Kubernetes介绍

文章目录1.常见容器编排工具2.kubernetes简介3.kubernetes组件4.kubernetes概念1.常见容器编排工具 Swarm&#xff1a;Docker自己的容器编排工具Mesos&#xff1a;Apache的一个资源统一管控的工具&#xff0c;需要和Marathon结合使用Kubernetes&#xff1a;Google开源的的容器…

vector的实现和使用中的常见错误

文章目录实现构造函数时的调用模糊实现insert函数时的迭代器失效使用erase函数时的迭代器失效实现reserve函数使用memcpy函数导致的浅拷贝实现构造函数时的调用模糊 vector的构造函数有这四种,其中有两种在实例化的时候会有调用模糊的问题&#xff1a; vector<int> v(10…