【问题标题】:How does Arrays work in the ByteCode of Java [duplicate]数组如何在 Java 的字节码中工作 [重复]
【发布时间】:2014-01-05 21:58:40
【问题描述】:

如果我使用普通类,如 List、Vector 或其他东西,我会得到一个 size() 函数,它返回相关类的长度,但如果我使用类的数组或默认数据类型,我会得到一个 public成员长度返回数组的当前长度。

int a[] = new int[3];
a.length; // used without ()

Vector<Integer> v = new Vector<Integer>();
v.length(); // used with ()

这是为什么呢?我的意思是数组不是自己的类,不是吗?因此,如果它不是类,它就不能有成员变量。我不知道如何在后台处理它(字节码)。我知道内存中的数组存储有指向数组第一个元素的指针,并且索引 (i) 内存指针被移动到ArrayPointer + i*(size of DataType)

现在您可以说计算机遍历数组的所有元素并计算所有元素,但是计算机如何知道数组的结束位置和下一个数组的开始位置?存储大小的数组中的“成员变量”从哪里来?

我的意思是我们经常使用数组,但我对 ByteCode 中 Java 代码背后发生的事情知之甚少。你能告诉我这是怎么可能的吗?

【问题讨论】:

标签: java arrays bytecode


【解决方案1】:

数组是 Java 中的对象,但它们并不对应于真正的类。实际上,JVM 会在运行中隐式创建数组类,但出于性能原因,它们不是实际的类。

由于它们是对象,因此可以将它们存储在 Object 字段中并像往常一样传递。但是,它们在字节码级别的处理方式略有不同。

首先,使用newarrayanewarraymultinewarray 指令分别为一维原始数组、一维对象和多维数组创建数组。相比之下,常规对象是使用new 指令创建的。

使用*aload*astore 指令完成获取和设置元素。

另外,x.length 不是一个真实的字段。相反,它被编译为arraylength 指令。这可以通过编译以下代码看到。

public void test(int size){
    int[] x = new int[size];
    String[] y = new String[size];
    System.out.println(x.length);
    System.out.println(y.length);
}

产生以下字节码

.method public test : (I)V
    .limit stack 2
    .limit locals 4
    iload_1
    newarray int
    astore_2
    iload_1
    anewarray java/lang/String
    astore_3
    getstatic java/lang/System out Ljava/io/PrintStream;
    aload_2
    arraylength
    invokevirtual java/io/PrintStream println (I)V
    getstatic java/lang/System out Ljava/io/PrintStream;
    aload_3
    arraylength
    invokevirtual java/io/PrintStream println (I)V
    return
.end method

尝试通过手动创建字节码来访问length 字段将导致异常,因为该字段实际上并不存在。

.method static public main : ([Ljava/lang/String;)V
    .limit stack 1
    .limit locals 1
    aload_0
    getfield [Ljava/lang/String; length I
    return
.end method

结果

Exception in thread "main" java.lang.VerifyError: Expecting reference to class i
n class ArrayTest at constant pool index 30 in method ArrayTest.main([Ljava/lang
/String;)V
        at java.lang.Class.getDeclaredMethods0(Native Method)
        at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
        at java.lang.Class.getMethod0(Unknown Source)
        at java.lang.Class.getMethod(Unknown Source)
        at sun.launcher.LauncherHelper.getMainMethod(Unknown Source)
        at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)

【讨论】:

  • 哇,谢谢。这就是我想要的答案,以了解 Java 代码背后的逻辑。非常感谢:)
  • @Cilenco 如果是这样,您应该接受答案。
  • 所以澄清一下,除了使用.length之外,我们没有办法在Java中访问数组的长度?
  • @Alex 据我所知,是的。从技术上讲,您可以通过本机代码来实现,但这可能不是您的意思。
  • 我最近一直在看 Java 字节码,想知道 arraylength 是一个常量还是 JVM 每次都主动计算数组中的每个元素?
【解决方案2】:

java 中的数组有一个类,因此是对象。构建它的方式与“普通”类不同。更多内容可能在

Why isn't there a java.lang.Array class? If a java array is an Object, shouldn't it extend Object?

【讨论】:

    【解决方案3】:

    数组实际上是一个类,它像任何其他类一样扩展 Object,但为了更好的性能和便利性,它由语言在内部处理。作为一个类,它可以有字段和方法。

    【讨论】:

      【解决方案4】:

      数组是一个对象,您可以自己尝试,只需将数组分配给对象值即可。 例如:

      public Object test(){
        int[] test = new int[5];
        Object obj = test; 
        return obj;
      }
      

      如果你想知道一个字节级别的数组是什么:数组本身只是一个指向数组长度信息的指针,一些其他信息和一个指向保留内存的指针可以存储数据(数组大小的顺序内存)。访问数据很简单,只需查找 [pointer]+[indexnumber]*[sizeofeachitem],然后您就知道要查看哪块内存。

      【讨论】:

        【解决方案5】:

        数组类型实际上是类!存储在内存中时,它们包含了它们的基本类信息(如果它们不是原生数组,则包括其包含元素的类信息)、它们持有的元素数量,以及实际的数组数据。数组对象中的lengthfinal 字段而不是方法,因为数组永远不会包含不同数量的元素。

        对于其他类型的结构,比如Vector,对象中元素的数量可以随时变化,所以length()必须是一个方法。与String 类似:即使String 类是不可变的,它仍从CharSequence 继承length() 方法;某些类型的CharSequence 的长度可以是可变的,因此它再次被实现为一个方法。

        【讨论】:

        • x.length 被编译为 arraylength 指令。这不是一个真正的领域。
        • @Antimony 数组的长度实际上存储在其数据的头部,所以即使它不与其他类一样对待也可能。即使是有效实现数组类的文档也将length 称为public final 字段,即使它在JVM 中被视为低级数据。
        • 除了问题问的是字节码级别,而在字节码级别,没有这个字段。它是作为特殊指令实现的。
        • 这只是我误解了这个问题;我推测他们正在寻找内存中数据的字节表示,而不是使用的汇编指令。
        猜你喜欢
        • 2016-12-27
        • 1970-01-01
        • 1970-01-01
        • 2011-09-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-10-05
        • 2013-05-02
        相关资源
        最近更新 更多