我的后端学习大纲
JVM学习大纲
一、加载与存储指令:
加载:主要是指的将数据压入到操作数栈中,那么这个数据可能来源于常量池也可能来自于局部变量表
1.iload_0占1个字节。iload 0 占3个字节。
2.因为操作码数量有限,只有256个,所以只把常用的0-3设置成了操作码。
2.1.复习操作数栈
2.2.局部变量压栈指定:
- 1.局部变量压栈指令将给定的局部变量表中的数据压入操作数栈。
- 2.这类指令大体可以分为:
> xload_<n>(x为i、1、f、d、a,n为0到3>
> xload (x为i、1、f、d、a)
说明:在这里,x的取值表示数据类型。
指令xload_n表示将第n个局部变量压入操作数栈,比如iload_1、fload_0、aload_0等指令。其中aload_n表示将一个对象引用压栈。
指令xload通过指定参数的形式,把局部变量压入操作数栈,当使用这个命令时,表示局部变量的数量可能超过了4个,比如指令iload、fload等。
2.3.常量入栈指令:
常量入栈指令的功能是将常数压入操作数栈,根据数据类型和入栈内容的不同,又可以分为const系列、push系列和ldc指令。
指令const系列:用于对特定的常量入栈,入栈的常量隐含在指令本身里。指令有: iconst_ (i从-1到5)、lconst_(1从0到1)、fconst_(f从0到2)、dconst_(d从0到1)、aconst_null。
比如,
iconst_m1将-1压入操作数栈;iconst_x (x为0到5)将x压入栈:
lconst_0、lconst_1分别将长整数9和1压入栈;
fconstl 0、fconst_1、fconst_2分别将浮点数0、1、2压入栈;dconst_0和dconst_1分别将double型o和1压入栈。
aconst_null将null压入操作数栈;
从指令的命名上不难找出规律,指令助记符的第一个字符总是喜欢表示数据类型,i表示整数,1表示长整数,f表示浮点数,d表示双精度浮点,习惯上用a表示对象引用。如果指令隐含操作的参数,会以下划线形式给出。
指令push系列:主要包括bipush和sipush。它们的区别在于接收数据类型的不同,bipush接收8位整数作为参数,sipush接收16位整数,它们都将参数压入栈。
指令ldc系列:如果以上指令都不能满足需求,那么可以使用万能的ldc指令,它可以接收一个8位的参数,该参数指向常量池中的int、float或者String的索引,将指定的内容压入堆栈。
类似的还有ldc_w,它接收两个8位参数,能支持的索引范围大于ldc。
如果要压入的元素是long或者double类型的,0使用ldc2_w指令,使用方式都是类似的。
2.4.出栈装入局部变量表指令:
出栈装入局部变量表指令用于将操作数栈中栈顶元素弹出后,装入局部变量表的指定位置,用于给局部变量赋值。
这类指令主要以store的形式存在,比如xstore (x为i、l、f、d、a)、 xstore_n (x 为i、1、f、d、a, n为0至3)
其中,指令istore_n将从操作数栈中弹出一个整数,并把它赋值给局部变量索引n位置。
指令xstore由于没有隐含参数信息,故需要提供一个byte类型的参数类指定目标局部变量表的位置。
说明:
一般说来,类似像store这样的命令需要带一个参数,用来指明将弹出的元素放在局部变量表的第几个位置。但是,为了尽可能压缩指令大小,使用专门的istore_1指令表示将弹出的元素放置在局部变量表第1个位置。类似的还有
istore_0、istore_2、istore_3,它们分别表示从操作数栈顶弹出一个元素,存放在局部变量表第0、2、3个位置。
由于局部变量表前几个位置总是非常常用,因此这种做法虽然增加了指令数量,但是可以大大压缩生成的字节码的体积。如果局部变量表很大,需要存储的槽位大于3,那么可以使用istore指令,外加一个参数,用来表示需要存放的槽位位置。
二、算数指令:
2.1.作用:
算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新压入操作数栈
2.2.分类:
大体上算术指令可以分为两种:对整型数据进行运算的指令与对浮点类型数据进行运算的指令。
2.3.byte、short、char和boolean类型说明
- 1.在每一大类中,都有针对Java虚拟机具体数据类型的专用算术指令。但没有直接支持byte、short、char和boolean类型的算术指令,对于这些数据的运算,都使用int类型的指令来处理。
- 2.此外,在处理boolean、byte、short和char类型的数组时,也会转换为使用对应的int类型的字节码指令来处理
2.4.运算时的溢出
- 1.数据运算可能会导致溢出,例如两个很大的正整数相加,结果可能是一个负数。其实Java虚拟机规范并无明确规定过整
型数据溢出的具体结果,仅规定了在处理整型数据时,只有除法指令以及求余指令中当出现除数为o时会导致虚拟机抛出异常ArithmeticException。
2.5.运算模式
向最接近数舍入模式:JVM要求在进仃浮点数T昇的,所有色界结木培近览代牛件择最低有效位为零的;
舍入为可被表示的最接近的精确值,如果有两种可表示的形式与该值一样接近,将优先选择最低有效位为零的;
向零舍入模式:将浮点数转换为整数时,采用该模式,该模式将在目标数值类型中选择一个最接近但是不大于原值
的数字作为最精确的舍入结果;
2.6.NaN值使用
当一个操作产生溢出时,将会使用有符号的无穷大表示,如果某个操作结果没有明确的数学定义的话,将会使用NaN值来表示。而且所有使用NaN值作为操作数的算术操作,结果都会返回 NaN;
i++与++i的问题
对于不涉及赋值操作的i++和++i并无区别,字节码一模一样
验证java基础所学的++在前,先++后赋值;++在后,先赋值,后++。
根据字节码理解
先把10压入操作数栈,然后把10放入局部变量表1的位置,然后把局部变量表1的位置的元素加载到操作数栈,然后对局部变量表1的位置的元素加一,然后把操作数栈中的元素保存到局部变量表2的位置。
先把20压入操作数栈,然后把20放入局部变量表3的位置,然后对局部变量表3的位置的元素加一,然后把局部变量表3的位置的元素加载到操作数栈,然后把操作数栈中的元素保存到局部变量表4的位置。
为啥打印出来是10?局部变量表中变为了11,但随后又被覆盖了成了10。
比较指令