JVM是如何得到数组长度的

JVM是如何得到数组长度的

这篇笔记主要记录下我们在java中编写的数组,比如int arr[] ={1,2,3};,那么我们使用int len = arr.length;jvm底层是如何得到数组长度的;
数组在jvm底层是动态 产生的,也就是说不是静态的,类似于我们得到一个对象的hashcode一样,如果你没有重写过hashcode方法,那么对象的hashcode默认就是对象的内存地址,所以hashcode也是动态产生的,我们的数组也是一样的,我们定义好数组过后,数组在运行时是可以动态生成、动态添加以及动态删除的,所以数组的长度也是动态产生的;我们这边通过程序和HSDB来证明数组的长度在jvm底层是如何得到的,先看以下程序:

public class T0819 {


    public static void main(String[] args) {
        int arr [] ={1,2,3};
        int length = arr.length;
        System.out.println(length);
    }
}

我们查看下字节码信息,通过javap -verbose T0819.class

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=3, args_size=1
         0: iconst_3
         1: newarray       int
         3: dup
         4: iconst_0
         5: iconst_1
         6: iastore
         7: dup
         8: iconst_1
         9: iconst_2
        10: iastore
        11: dup
        12: iconst_2
        13: iconst_3
        14: iastore
        15: astore_1
        16: aload_1
        17: arraylength
        18: istore_2
        19: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        22: iload_2
        23: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        26: return
      LineNumberTable:
        line 7: 0
        line 8: 16
        line 9: 19
        line 10: 26
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      27     0  args   [Ljava/lang/String;
           16      11     1   arr   [I
           19       8     2 length   I
}

看程序计数器17,arraylength,说白了要搞清楚数组长度在jvm中是如何动态得到的就要看jvm是如何处理字节码指令arraylength的;我这边找到openjdk是如何获取数组长度的一段代码块:

CASE(_arraylength):
{
  arrayOop ary = (arrayOop) STACK_OBJECT(-1);
  CHECK_NULL(ary);
  SET_STACK_INT(ary->length(), -1);
  UPDATE_PC_AND_CONTINUE(1);
}


int length() const {
    return *(int*)(((intptr_t)this) + length_offset_in_bytes());
 }

CASE代码中就是获取我们的数组长度的,其中case中的第三行代码就是获取长度然后压入栈
我们主要来分析下lengnth()这个方法,其中通过this指针得到数组的首位置,然后加上数组的偏移量进行相加就得到我们的数组长度 ,我们看下图:
在这里插入图片描述
((intptr_t)this)=得到我们数组的首位置
length_offset_in_bytes()=得到我们数组在内存中的偏移量
然后相加,取地址就得到了我们的数组长度

我们再来看下获取偏移量这个函数length_offset_in_bytes()

//如果不压缩,则在arrayOopDesc中声明的非静态字段之后分配
//如果压缩,它将占用oopDesc中_klass字段的后半部分
static int length_offset_in_bytes() {
    return UseCompressedClassPointers ? klass_gap_offset_in_bytes() :
                               sizeof(arrayOopDesc);
  }

看上面代码有个参数UseCompressedClassPointers ,这个参数也是我们jvm调优的一部分,这个参数是什么意思呢?我们再上面的笔记中知道我们的对象内存布局有对象头,实例数据和对齐填充;
而对象头又分为mark word、类型指针和数组长度,而类型指针压缩和不压缩的字节数是不一样的,
那么UseCompressedClassPointers 这个参数和UseCompressedoops有什么区别呢?
UseCompressedoops是压缩的是对象指针的长度,而UseCompressedClassPointers 压缩的是Klass对象的指针的长度,如果我们开启了UseCompressedoops,那么UseCompressedClassPointers 默认开启,
也就是说UseCompressedoops包含UseCompressedClassPointers 。
首先我们来分析下数组:
对应的Klass是:TypeArrayKlass实例
对应的oop是:TypeArrayOop实例

而类型指针在jvm中的代码块是:

union _metadata {
    Klass*      _klass;         8B
    narrowKlass _compressed_klass;    4B
  } _metadata;

它是一个联合体,整个联合体占用8B的空间,如果我们是压缩的,那么会使用4B,如果我们不压缩,那么会使用8B,那么我们如果压缩了的话,那是不是就浪费了空间,length_offset_in_bytes()方法的注释已经写上了,我们下面就开始分析整个问题。
对象的内存布局
对象头
Mark Word
Klass pointer
数组长度
实例数据
填充

我们如果开启了指针压缩的情况下:
Mark Word 8B
Klass pointer 4B
数组长度 4B
那么指针长度 + 数组长度就是=4B+4B=8B
如果我们不压缩就是=8B+4B=12B

所以我们理解下代码注释中的这句话“如果压缩,它将占用oopDesc中_klass字段的后半部分”
如果压缩,类型指针4B,因为我们的

union _metadata {
    Klass*      _klass;         8B
    narrowKlass _compressed_klass;    4B
  } _metadata;

是8B,压缩了使用的就是联合体中的_compressed_klass,而我们的数组长度是4B,所以说就是 用的_klass中后半段4B,就是这个意思,也就是说我们的数组长度用的是联合体中的后半段,如果开启了指针压缩。

我们来通过HSDB来查看下我们的数组长度:
在这里插入图片描述
我们是开启了指针压缩的,所以是——metadata._compressed_klass,我们再来查看下它的内存视图:
在这里插入图片描述
我们关闭指针压缩:-XX:-UseCompressedClassPointers
在这里插入图片描述
一看上图就是知道是关闭了指针压缩的
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值