JAVA和Nginx 教程大全

网站首页 > 精选教程 正文

面向JVM编译-类实例、数组的创建和使用

wys521 2024-11-16 01:46:37 精选教程 28 ℃ 0 评论


在上一篇文章中提到过:在JVM级别,一个构造函数就是一个名称为<init>(名称是由编译器提供的)的方法,这个特殊的方法就是类实例的初始化方法。如果一个类有多个构造函数,那么就会有多个对应的实例初始化方法。当一个类实例被创建,类变量已经被初始化了默认值,实例初始化方法就会被调用。

一个类实例被创建的过程中都发生了什么?数值数组、引用类型数组、一位数组、多维数组是怎样创建的?又是如何使用和操作的?这一章节详细描述类实例以及对象的创建和使用过程。

实例对象的创建和使用

类实例的创建和传递

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.<init>()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.<init>()V 从操作数栈弹出一个引用作为this传入MyObj类的类实例初始化方法,并执行初始化
7    astore_1                  //弹出栈顶的类实例引用,存到局部变量表索引为1的位置,即变量o
8    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)不是类实例中字段的偏移量。编译器生成对实例字段的符号引用,这些字段存储在运行时常量池中。这些运行时常量池项在运行时被解析,以确定字段的实际位置。

数组的创建和使用

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的位置,存储整型值12
15   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。如果你已经理解了实例方法如何被调用,就不会对此处有疑惑,否则建议先回顾下之前讲方法调用的章节。

引用类型数组

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.<init>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的位置,即变量grid
8    aload_1                      //将局部变量1中的数组索引压入操作数栈
9    areturn                      //从操作数栈中弹出数组索引,返回到调用者栈帧的操作数栈中

multianewarry指令的第一个操作数是一个运行时常量池的索引,指向还要创建的数组的类型,第二个参数代表要创建的数组的维度。因为代码中只指定了前两个维度的数组长度,第三个维度并没有指定长度,因此jvm实际只会创建前两个维度的数据,并从堆内存中分配空间。当我们把new int[10][5][]替换为new int[10][5][2],编译后代码就会变为multianewarry #1,3。

多维数组也是一个对象,分别由aload_1和areturn指令进行加载压栈和返回。所有数组都有自己的长度,可以通过arraylength指令访问。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表