码神手记-资深攻城狮的私房笔记,微信公众平台/知乎/头条同步。
动动小手,点个关注!
![a77a6a5f08f932c80ba577abc46d3b58.gif](https://i-blog.csdnimg.cn/blog_migrate/ebc9b63903d975f605f99ad7b18bb744.gif)
面向JVM编译-类实例、数组的创建和使用
在上一篇文章中提到过:在JVM级别,一个构造函数就是一个名称为(名称是由编译器提供的)的方法,这个特殊的方法就是类实例的初始化方法。如果一个类有多个构造函数,那么就会有多个对应的实例初始化方法。当一个类实例被创建,类变量已经被初始化了默认值,实例初始化方法就会被调用。
一个类实例被创建的过程中都发生了什么?数值数组、引用类型数组、一位数组、多维数组是怎样创建的?又是如何使用和操作的?这一章节详细描述类实例以及对象的创建和使用过程。
1
实例对象的创建和使用
类实例的创建和传递
JVM使用new指令创建类实例,直接上例子:
Object create(){ return new Object();}
编译后:
Method java.lang.Object create()0 new #1 //Class java.lang.Object 创建一个Object类的实例并将引用压入操作数栈。#1指向当前类运行时常量池中的一个Object类型引用3 dup //将栈顶的类实例引用复制为两个(后续指令要用到两次实例的引用)4 invokespecial #4 // Method java.lang.Object.()V。从操作数栈弹出一个引用作为this传入Object类的类实例初始化方法,并执行初始化7 areturn //返回栈顶的类实例引用
类实例作为引用类型被传递和返回,这非常类似于数值类型。
int i; // 一个实例变量MyObj example(){ MyObj o=new MyObj(); return silly(o);}MyObj silly(MyObj o){ if (o!=null){ return o; }else{ return o; }}
编译后:
Method MyObj example()0 new #2 // Class MyObj 创建一个MyObj类的实例并将引用压入操作数栈。#1指向当前类运行时常量池中的一个MyObj类型引用3 dup //将栈顶的类实例引用复制为两个(后续指令要用到两次实例的引用)4 invokespecial #5 // Method MyObj.()V 从操作数栈弹出一个引用作为this传入MyObj类的类实例初始化方法,并执行初始化7 astore_1 //弹出栈顶的类实例引用,存到局部变量表索引为1的位置,即变量o8 aload_0 //将局部变量表索引0处的值(即example方法调用者类实例的引用)压入操作数栈9 aload_1 //将局部变量表索引1出的值(即MyObj类实例的引用)压入操作数栈10 invokespecial #4 //调用silly方法,并将结果压入操作数栈。#4指向当前类运行时常量池中的方法引用,指向silly方法13 areturn //返回栈顶的对象引用Method MyObj silly(MyObj)0 aload_1 //将局部变量表索引为1的值(即传入的MyObj类实例引用)压入操作数栈1 ifnull 6 //如果栈顶对象是null,则控制流转到指令数组索引6处的指令,否则继续执行后续指令4 aload_1 //将局部变量表索引为1的值(即传入的MyObj类实例引用)重新压入操作数栈5 areturn //从栈顶弹出MyObj类实例的引用,压入到调用者栈帧的操作数栈,程序结束。6 aload_1 //将局部变量表索引为1的值(即传入的MyObj类实例引用)压入操作数栈7 areturn //从栈顶弹出MyObj类实例的引用,压入到调用者栈帧的操作数栈,程序结束(此时返回的引用是null)。
类实例字段的访问
JVM使用getfield和putfield指令访问类实例的字段(实例变量)。假设i是一个int类型的实例变量,那么setIt()和getIt()方法定义如下:
void setIt(int value){ i=value;}int getIt(){ return i;}
编译后:
Method void setIt(int)0 aload_0 //将局部变量表索引为0的值(一个类实例引用)压入操作数栈【准备类实例引用】1 iload_1 //将局部变量表索引为1的值(一个整型数值)压入操作数栈【准备字段值】2 putfield #4 //Field Example.i I //将字段值赋给类实例引用的指定字段,即i字段。#4是类实例字段的符号引用5 return //结束返回Method int getIt()0 aload_0 //将局部变量表索引为0的值(一个类实例引用)压入操作数栈1 getfield #4 //Field Example.i I //获取类实例的指定字段值(i的值),压入操作数栈4 ireturn //从操作数栈弹出整型数值并返回
与方法调用指令的操作数一样,putfield和getfield指令的操作数(运行时常量池索引#4)不是类实例中字段的偏移量。编译器生成对实例字段的符号引用,这些字段存储在运行时常量池中。这些运行时常量池项在运行时被解析,以确定字段的实际位置。
2
数组的创建和使用
JVM数组也是对象,有专门的指令集用于数组的创建和操作。
数值类型数组
newarray指令用于创建数值类型的数组。
void createBuffer() { int buffer[]; int bufsz = 100; int value = 12; buffer = new int[bufsz]; buffer[10] = value; value = buffer[11];}
编译后:
Method void createBuffer()0 bipush 100 //将整型常量100压入操作数栈2 istore_2 //从操作数栈弹出整型常量100,放入局部变量表索引2的位置3 bipush 12 //将整型常量12压入操作数栈4 istore_3 //从操作数栈弹出整型常量12,放入局部变量表索引3的位置6 iload_2 //将局部变量2(整型常量100)压入操作数栈7 newarray int //从操作数栈中弹出100作为数组长度,创建一个int类型数组,并将引用压入操作数栈9 astore_1 //将数组引用弹出栈,放到局部变量表索引1的位置,即buffer变量的位置,buffer指向了新创建的数组对象10 aload_1 //将局部变量1中的数组引用压入操作数栈 【准备引用】11 bipush 10 //将整型常量10压入操作数栈 【准备索引】13 iload_3 //将局部变量3(整型值12)压入操作数栈 【准备数值】14 iastore //在数组索引为10的位置,存储整型值1215 aload_1 //再将数组引用压入操作数栈【准备引用】16 bipush 11 //将整型常量11压入操作数栈 【准备索引】18 iaload //将数组中索引为11的数值压入操作数栈,即buffer[11]19 istore_3 //从操作数栈弹出buffer[11]的值,作为整数存储到局部变量3,即value变量。
在本次对createBuffer方法的调用中,栈帧局部变量表中的变量依次是:this、buffer、bufsz、value,对应的索引是0、1、2、3。如果你已经理解了实例方法如何被调用,就不会对此处有疑惑,否则建议先回顾下面向JVM编译-编译器如何处理方法调用?。
引用类型数组
anewarray指令用于创建对象引用的一维数组,例如:
void createThreadArray() { Thread threads[]; int count = 10; threads = new Thread[count]; threads[0] = new Thread();}
编译后:
Method void createThreadArray()0 bipush 10 //将整型常量10压入操作数栈2 istore_2 //从操作数栈弹出整型常量10,放入局部变量表索引2的位置,即count变量3 iload_2 //将count变量值(10)压入操作数栈4 anewarray class #1 //创建一个Thread类型的数组,长度为10,将数组引用压入操作数栈7 astore_1 //从操作数栈弹出数组引用,放入局部变量表索引1的位置,即threads变量,threads指向了新创建的数组对象8 aload_1 //将thread变量中的数组引用压入操作数栈【准备数组引用】9 iconst_0 //将整型常量0压入操作数栈【准备数组索引】10 new #1 //创建一个Thread类实例,引用压入操作数栈【准备数组元素值】13 dup //复制栈顶Thread实例的引用,作为this参数提供给invokespecial指令使用。(此时操作数栈栈顶有两个相同的Thread实例引用,指向同一个对象)14 invokespecial #5 //调用Thread类实的构造函数,即类实例初始化方法,弹出栈顶的Thread实例引用,传入java.lang.Thread.V并执行函数。17 aastore //将Thread实例的引用存入数组索引0的位置18 return
多维数组
anewarray指令还可以用于创建多维数组中的一个维度,multianewarray则可以用来一次性创建多个维度,比如,三维矩阵:
int[][][] create3DArray() { int grid[][][]; grid = new int[10][5][]; return grid;}
编译后:
Method int create3DArray()[][][]0 bipush 10 //将整型常量10压入操作数栈(第一个维度的长度)2 iconst_5 //将整型常量5压入操作数栈(第二个维度的长度)3 multianewarray #1,2 //创建数组并压入操作数栈。#1指向了运行时常量池中的一个类型:[[[I。从操作数栈中获取前两个维度的数组长度,分别是10和5。7 astore_1 //从操作数栈中弹出数组的引用,放入局部变量表索引1的位置,即变量grid8 aload_1 //将局部变量1中的数组索引压入操作数栈9 areturn //从操作数栈中弹出数组索引,返回到调用者栈帧的操作数栈中
multianewarry指令的第一个操作数是一个运行时常量池的索引,指向还要创建的数组的类型,第二个参数代表要创建的数组的维度。因为代码中只指定了前两个维度的数组长度,第三个维度并没有指定长度,因此jvm实际只会创建前两个维度的数据,并从堆内存中分配空间。当我们把new int[10][5][]替换为new int[10][5][2],编译后代码就会变为multianewarry #1,3。
多维数组也是一个对象,分别由aload_1和areturn指令进行加载压栈和返回。所有数组都有自己的长度,可以通过arraylength指令访问。
扫描二维码关注
![f50fd9193c31fb81c449339d8d539511.png](https://i-blog.csdnimg.cn/blog_migrate/f331575c062c9b0b060c06982e597c31.jpeg)
码神手记
微信号 : iSuperCoder