JVM004_字节码指令简介

字节码指令简介

Java虚拟机指令由操作码(Opcode)和跟随其后的零至多个操作数(Operand)组成。

  • 操作码:一个字节长度的,代表某种特定操作含义的数字。

  • 操作数:操作码需要的参数。

字节码与数据类型

opcodebyteshortintlongfloatdoublecharreference
Tstoreistorelstorefstoredstoreastore
Tinciinc
Taloadbaloadsaloadialoadlaloadfaloaddaloadcaloadaaload
Tastorebastoresastoreiastorelastorefastoredastorecastoreastore
Taddiaddladdfadddadd
Tsubisublsubfsubdsub
Tmulimullmulfmuldmul
Tdividivldivfdivddiv
Tremiremlremfremdrem
Tnegineglnegfnegdneg
Tshlishllshl
Tshrishrlshr
Tushriushrlushr
Tandiandland
Toriorlor
Txorixorlxor
i2Ti2bi2si2li2fi2d
l2Tl2il2fl2d
f2Tf2if2lf2d
d2Td2id2ld2f
Tcmplcmp
Tcmplfcmpldcmpl
Tcmpgfcmpgdcmpg
if_TempOPif_iempOPif_aempOP
Treturnireturnlreturnfreturndreturnareturn

大部分指令都没有支持整数类型byte、 char和short, 没有任何指令支持boolean类型。 编译器会在编译期或运行期将byte和short类型的数据带符号扩展(Sign-Extend) 为相应的int类型数据, 将boolean和char类型数据零位扩展(Zero-Extend) 为相应的int类型数据。 与之类似, 在处理boolean、 byte、 short和char类型的数组时, 也会转换为使用对应的int类型的字节码指令来处理。 因此, 大多数对于boolean、 byte、 short和char类型数据的操作, 实际上都是使用相应的对int类型作为运算类型(Computational Type) 来进行的。

加载和存储指令

加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输 。

  • 将一个局部变量加载到操作栈:iload,iload_0,iload_1,iload_2,iload_3,lload,lload_0,lload_1,lload_2,lload_3,fload_0,fload_1,fload_2,fload_3,dload,dload_0,dload_1,dload_2,dload_3,aload,aload_0,aload_1,aload_2,aload_3

    iload_0 表示将第0个变量槽中的int型变量推到操作数栈栈顶。

    iload_1 表示将第1个变量槽中的int型变量推到操作数栈栈顶。

    iload 后面会跟一个参数n,表示将第n个变量槽中的int行变量推送到操作数栈栈顶。

    其余的指令同理。

  • 将一个数值从操作数栈存储到局部变量表中:

    istore、 istore_<n>、 lstore、 lstore_<n>、 fstore、fstore_<n>、 dstore、 dstore_<n>、 astore、 astore_<n>
    
  • 将一个常量加载到操作数栈:

    bipush、 sipush、 ldc、 ldc_w、 ldc2_w、 aconst_null、 iconst_m1、iconst_<i>、 lconst_<l>、 fconst_<f>、 dconst_<d>
    
  • 扩充局部变量表的访问索引的指令

    wide #拓展本地变量的宽度,用于修改其他指令行为
    

    public class Test2{public int inc(int a,int b,int c,int d,int e,int f,int h,int j,int k){int z = a + 9;return z;}
    }
    

    利用javac -g Test2.java编译该源文件,再利用javap -v Test2.class反编译:

    public int inc(int, int, int, int, int, int, int, int, int);descriptor: (IIIIIIIII)Iflags: ACC_PUBLICCode:stack=2, locals=11, args_size=100: iload_1				# 将第1个变量槽中的int型变量推送到栈顶,即将变量a推送到栈顶1: bipush        9		# 将int型常量9推送到栈顶3: iadd				# 将栈顶的两个int型数值相加并压入栈顶,即a+94: istore        10    # 将栈顶int型数值存入指定的本地变量槽,及将a+9的结果赋值给变量Z6: iload         10    # 将第10个变量槽中的变量推送值栈顶8: ireturn				# 弹栈,从当前方法返回int,及return z;LineNumberTable:line 3: 0line 4: 6LocalVariableTable:Start  Length  Slot  Name   Signature0       9     0  this   LTest2;0       9     1     a   I0       9     2     b   I0       9     3     c   I0       9     4     d   I0       9     5     e   I0       9     6     f   I0       9     7     h   I0       9     8     j   I0       9     9     k   I6       3    10     z   I
    

运算指令

运算指令用于将操作数栈上的两个值进行某种特定的运算,并将结果重新压入操作栈顶。可分为对整型数据进行运算的指令和对浮点型数据进行运算指令。

不存在直接支持byte、 short、 char和boolean类型的算术指令 ,而是使用操作int类型的指令代替。

#加法指令:	iadd、 ladd、 fadd、 dadd
#减法指令:  isub、 lsub、 fsub、 dsub
#乘法指令:  imul、 lmul、 fmul、 dmul
#除法指令:  idiv、 ldiv、 fdiv、 ddiv
#求余指令:  irem、 lrem、 frem、 drem
#取反指令:  ineg、 lneg、 fneg、 dneg
#位移指令:  ishl、 ishr、 iushr、 lshl、 lshr、 lushr
#按位或指令: ior、 lor
#按位与指令: iand、 land
#按位异或指令: ixor、 lxor
#局部变量自增指令: iinc
#比较指令: dcmpg、 dcmpl、 fcmpg、 fcmpl、 lcmp

类型转换指令

类型转换指令可以将两种不同的数值类型相互转换 。

**宽化类型转换:**小范围类型向大范围类型的安全转换。

以下数值类型的宽化类型转换无序显示的转换指令:

    1. int到long,float,double2. long到float,double3. float到double

窄化类型转换:窄化类型转换必须显示的使用转换指令来完成

i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l、d2f

窄化转换可能会导致转换结果产生不同的正负号、不同的数量级的情况,很可能会出现精度丢失。

JVM将浮点值转换为int或者long时,需要遵循以下规则:

  • 如果浮点值是NaN, 那转换结果就是int或long类型的0。
  • 如果浮点值不是无穷大的话, 浮点值使用IEEE 754的向零舍入模式取整, 获得整数值v。 如果v在目标类型T(int或long) 的表示范围之类, 那转换结果就是v; 否则, 将根据v的符号, 转换为T所能表示的最大或者最小正数。

细节参看:IEEE 754

对象创建与访问指令

·创建类实例的指令: new
·创建数组的指令: newarray、 anewarray、 multianewarray
·访问类字段(static字段, 或者称为类变量) 和实例字段(非static字段, 或者称为实例变量)的指令: getfield、 putfield、 getstatic、 putstatic
·把一个数组元素加载到操作数栈的指令: baload、 caload、 saload、 iaload、 laload、 faload、daload、 aaload
·将一个操作数栈的值储存到数组元素中的指令: bastore、 castore、 sastore、 iastore、 fastore、dastore、 aastore
·取数组长度的指令: arraylength
·检查类实例类型的指令: instanceof、 checkcast

操作数栈管理指令

·将操作数栈的栈顶一个或两个元素出栈: pop、 pop2
·复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶: dup、 dup2、 dup_x1、dup2_x1、 dup_x2、 dup2_x2
·将栈最顶端的两个数值互换: swap

控制转移指令

控制转移指令可以让Java虚拟机有条件或无条件地从指定位置指令(而不是控制转移指令) 的下一条指令继续执行程序, 从概念模型上理解, 可以认为控制指令就是在有条件或无条件地修改PC寄存器的值。

·条件分支: ifeq、 iflt、 ifle、 ifne、 ifgt、 ifge、 ifnull、 ifnonnull、 if_icmpeq、 if_icmpne、 if_icmplt、if_icmpgt、 if_icmple、 if_icmpge、 if_acmpeq和if_acmpne
·复合条件分支: tableswitch、 lookupswitch
·无条件分支: goto、 goto_w、 jsr、 jsr_w、 ret

方法调用和返回指令

·invokevirtual指令: 用于调用对象的实例方法, 根据对象的实际类型进行分派(虚方法分派) ,这也是Java语言中最常见的方法分派方式。
·invokeinterface指令: 用于调用接口方法, 它会在运行时搜索一个实现了这个接口方法的对象, 找出适合的方法进行调用。
·invokespecial指令: 用于调用一些需要特殊处理的实例方法, 包括实例初始化方法、 私有方法和父类方法。
·invokestatic指令: 用于调用类静态方法(static方法) 。
·invokedynamic指令: 用于在运行时动态解析出调用点限定符所引用的方法。 并执行该方法。 前面四条调用指令的分派逻辑都固化在Java虚拟机内部, 用户无法改变, 而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的

异常处理指令

athrow

处理异常是由异常表来完成的,而非字节码指令。

同步指令

Java虚拟机可以支持方法级的同步和方法内部一段指令序列的同步, 这两种同步结构都是使用管程(Monitor, 更常见的是直接将它称为“锁”) 来实现的。

方法级的同步是隐式的, 无须通过字节码指令来控制, 它实现在方法调用和返回操作之中。 虚拟机可以从方法常量池中的方法表结构中的ACC_SYNCHRONIZED访问标志得知一个方法是否被声明为同步方法。 当方法调用时, 调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置, 如果设置了, 执行线程就要求先成功持有管程, 然后才能执行方法, 最后当方法完成(无论是正常完成还是非正常完成) 时释放管程。 在方法执行期间, 执行线程持有了管程, 其他任何线程都无法再获取到同一个管程。 如果一个同步方法执行期间抛出了异常, 并且在方法内部无法处理此异常, 那这个同步方法所持有的管程将在异常抛到同步方法边界之外时自动释放。同步一段指令集序列通常是由Java语言中的synchronized语句块来表示的, Java虚拟机的指令集中monitorenter和monitorexit两条指令来支持synchronized关键字的语义, 正确实现synchronized关键字需要Javac编译器与Java虚拟机两者共同协作支持 .

下篇文章针对这点做实例分析。

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

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

相关文章

LeetCode(#26)————删除排序数组中的重复项

题目 给定一个排序数组&#xff0c;你需要在原地删除重复出现的元素&#xff0c;使得每个元素只出现一次&#xff0c;返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 示例 1: 给定数组 nums […

JVM005_synchronized、同步指令、管程、MESA

synchronized、同步指令、管程、MESA synchronized是Java的一个关键词&#xff0c;可以保证方法或者代码块在运行时&#xff0c;同一时刻只有一个方法可以进入到临界区&#xff0c;保证了原子性、可见性、有序性。 临界资源一次只能被一个线程访问的资源。 **临界区:**访问临…

MySQL————表维护相关低频操作总结

引言 一些对表结构的修改操作是日常数据库操作中非常低频的&#xff0c;这就好像盖完了一栋楼之后&#xff0c;很少会去动地基&#xff0c;不过这种情况在实际开发当中并非完全没有可能&#xff0c;因此&#xff0c;此篇博客主要总结表结构修改相关的语句。 一、备份表 如果…

JVM006_类加载的过程

类加载 类加载时机 类加载的过程 新术语 类加载器 简单的理解为将类转换为二进制流的类或接口。 数组的元素类型 数组去掉所有维度的类型。 数组的组件类型 数组去掉一个维度的类型。 基本块 按照控制流拆分的代码块。 1. 加载 加载是类加载过程的一个阶段。加载阶段主…

服务端开发——云服务器的端口转发设置(SSH隧道)

引言 本篇博客介绍端口转发的知识&#xff0c;并详细阐述操作和设置步骤。这是因为在实际工作中&#xff0c;会有很多企业从安全的角度考虑&#xff0c;为线上或重要的服务器设置一个跳板机&#xff08;堡垒机&#xff09;&#xff0c;避免远程开发人员直接操作&#xff0c;是…

JVM007_运行时栈帧结构

运行时栈帧结构 执行引擎是JVM的核心组件之一。 所有Java虚拟机的执行引擎输入输出都是一致的&#xff1a;输入的是字节码二进制流&#xff0c;处理过程是字节码解析执行的等效过程&#xff0c;输出的是执行结果。 JVM以方法作为作基本的执行单元。“栈帧”是用于支持虚拟机进…

Shiro————核心设计思想

引言 以此篇博客为引&#xff0c;开启一个新的专栏分类——Shiro。 之前在工作中有比较快速的学习过Shiro安全框架&#xff0c;但经过一年的荒废&#xff0c;已经不是很熟悉了&#xff0c;通过这个系列&#xff0c;深入研究和学习Shiro的一些知识&#xff0c;填补安全管理方面…

Web应用安全————账号冻结与 Session 实时失效

引言 开篇时说些题外话&#xff0c;最近刚刚被公司CY&#xff0c;不过很快找到了下家&#xff0c;也同时拿到了三家公司的Offer。一周面试下来&#xff0c;总体感觉面试题少了&#xff0c;不过多了上机程序题。新公司是做外包&#xff0c;不过相比于上一家公司&#xff0c;也算…

Web应用安全————Shiro 解决会话固定漏洞

引言 承接上一篇《Web应用安全————账号冻结与 Session 实时失效》关于 session 的学习&#xff0c;本篇博客聚焦如何通过 shiro 解决会话固定导致的漏洞问题。 首先&#xff0c;没怎么接触过应用安全方面的小伙伴可能会发起疑问 - 什么是会话固定&#xff1f; 简单来说&…

Web应用安全————多点登录互斥

引言 在实际生活中&#xff0c;很多网站都做了多点登录互斥的操作&#xff0c;简单来说就是同一个账号&#xff0c;只能在一台电脑上登录&#xff0c;如果有人在其他地方登录&#xff0c;那么原来登录的地方就会自动下线&#xff0c;再进行操作就会弹出登录界面。 实现思路 …

Shiro————会话管理

引言 本篇博客翻译自Shiro 官方网站的 Session Manager 手册。 网页地址&#xff1a;http://shiro.apache.org/session-management.html Shiro 会话管理支持的特性 基于POJO/J2SE&#xff08;IoC容器友好的&#xff09;- Shiro 中的所有东西都是基于接口的&#xff0c;而且…

Linux进阶之路————磁盘查询

引言 承接《Linux进阶之路————Linux磁盘分区与挂载》&#xff0c;本文介绍实际生产中对于磁盘的监控和查询。 一、查询磁盘整体使用情况 基本语法&#xff1a; df -h 该命令会显示包括我们手动挂载的磁盘&#xff0c;如果使用 umount 卸载磁盘&#xff0c;那么将不会显示…

Linux进阶之路————CentOS网络配置

引言 Linux在装机后&#xff0c;如果没有特殊配置&#xff0c;会使用动态获取 IP 地址的策略。本文描述了&#xff0c;虚拟机使用网络的拓扑图&#xff0c;以及如何通过配置&#xff0c;将 IP 地址固定下来&#xff0c;不会因为重启而失效。同时可以访问外网地址。 一、NAT模…

Linux进阶之路————进程与服务管理

引言 在Linux 中&#xff0c;每个执行的程序&#xff08;代码&#xff09;都成为一个进程&#xff0c;Linux 为每一个进程分配了一个唯一的 id 号 - PID。 每个进程都会对应一个父进程&#xff0c;而这个父进程可以复制多个子进程&#xff0c;例如 www 服务器。 每个进程都可…

Linux进阶之路———— RPM 与 YUM 包管理

引言 rpm 是一种用于互联网下载的打包及安装工具&#xff0c;它包含在某些 Linux 发行版中&#xff0c;生成具有 .rpm 扩展名的文件。rpm 是 redhat package manager&#xff08;RedHat 软件包管理器&#xff09;的缩写&#xff0c;类似 Windows 下的 setup.exe 文件。这一文件…

Linux进阶之路———Shell 编程入门

引言 通过 Shell 编程的学习&#xff0c;铺平架构师道路上的一块大砖。 Shell 在Linux 系统中的定位如下所示&#xff1a; 一、第一个 Shell 脚本 我们通过一个简单的 Shell 脚本来感受一下。 在 Shell 中不需要加 “;” 结尾&#xff0c;通过 vim 可以进行 shell 的编程工…

Linux 实操———CentOS 6 安装配置 Oracle JDK 1.8

引言 本篇博客也属于Linux进阶系列&#xff0c;主要讲解如何在CentOS 6 下安装并配置 JDK 8。由于通过 yum 搜索的结果都是 openjdk&#xff0c;而目前企业中还是以 Oracle jdk 为主&#xff0c;因此&#xff0c;操作步骤这样的。 在Oracle 官网把 jdk 1.8 下载下来&#xff…

Linux 实操———CentOS 6 安装配置 Tomcat

引言 Linux下安装Tomcat。 一、下载、传输与解压 同《Linux 实操———CentOS 6 安装配置 Oracle JDK 1.8》一样&#xff0c;前期都是先在远程机上下载压缩包&#xff0c;然后通过远程终端&#xff0c;将压缩包放在 Linux 的 opt 目录下&#xff0c;然后解压。 下载地址是T…

Spring Boot 实用开发技巧————Eclipse 远程调试

引言 在之前的开发当中&#xff0c;都会进行本地项目启动&#xff0c;然后向本地服务发起请求来进行 Debug 调试代码&#xff0c;这也是开发人员最常见的调试操作。但是当项目逐渐成型&#xff0c;慢慢的将各个模块部署到服务器后&#xff0c;调试的手段可能就仅仅剩下查看执行…

Linux 实操———— Shell 远程执行命令

引言 目前&#xff0c;开发人员的部署方式是&#xff0c;将项目打包(Maven 打包) 然后将 生成的 jar 包等文件&#xff0c;通过Xshell 等终端工具手动传输到远程服务器上&#xff0c;然后再通过在终端执行远程服务器上的 shell 脚本来启动服务。 本篇博客聚焦这样一种解决方案…