Java如何判断一个位置存储的是原始类型还是引用类型?

4
如果在堆上有一个对象,而该对象具有一些实例变量,其中一些是基本类型,另外一些是其他对象。因此,如果这个对象有5个字段,那么该对象在内存中的结构是怎样的呢?具体而言,Java在哪里存储每个字段的数据类型?是否有一些“标志字节”和一些“数据字节”,其中“标志字节”标识接下来的几个“数据字节”的数据类型?
我指的是一些更多的细节,超出了这个答案: https://dev59.com/GWIk5IYBdhLWcg3wDaYW#19623603 这个答案提供了关于数据本身如何存储在内存中的更多详细信息: https://dev59.com/iHI-5IYBdhLWcg3wVWzv#1907455 但它仍然没有说出标志存储在哪里,以表明数据类型是int/long/double/float/reference。

3
这是否指的是内存中的表示?我认为这在很大程度上取决于具体实现。事实上,我认为一旦JVM加载了被确定为有效的类文件,在严格意义上讲,它不必将此信息与实际实例数据“存储”在一起。这对执行来说根本不相关。当实例被内省时(例如使用反射),则可以在类数据中查找信息。然后...就在这里:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-FieldType - Marco13
我不知道这个问题的答案,但在汇编语言中,一个数据片段是被解释为指针还是面值,取决于作用于该数据的指令类型。将此扩展到Java,我猜测当Java将代码编译成字节码时,它会编译字节码指令以将引用类型视为指向其他内存位置的指针,将原始类型视为面值。虽然这只是一个猜测。 - jmrah
好的。因此,大致上JVM实现可以将20个字节的连续数据存储在内存中,并且应该从类中引用哪个字节应被解释为int,哪些字节应被解释为double-part1和double-part2?或者它可以通过JVM特定的方式进行处理以获得更好的性能...好的。 - Teddy
@jrahhali 这对我来说非常有帮助,可以让我更好地理解。由于编译器生成的字节码或汇编指令可能与变量类型相关/特定,因此该变量在运行时可能根本不在内存中。 - Teddy
2个回答

1
Here is a more concrete answer, that I am afraid still doesn't answer all of your question. Here is the link from the java 7 docs, with the relevant section being "2.11. Instruction Set Summary": https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html I will copy & paste some of it:
2.11.1. Types and the Java Virtual Machine Java虚拟机指令集中的大多数指令都编码了它们执行操作的类型信息。例如,iload指令(§iload)将一个本地变量(必须为int类型)的内容加载到操作数栈上。fload指令(§fload)将一个float值进行同样的操作。这两个指令可能具有相同的实现方式,但是具有不同的操作码。
对于大多数带类型的指令,指令类型在操作码助记符中都用一个字母来表示: i表示int操作,l表示long,s表示short,b表示byte,c表示char,f表示float,d表示double,a表示引用。
加载和存储指令在Java虚拟机框架(§2.6)的本地变量(§2.6.1)和操作数栈(§2.6.2)之间传输值。
访问对象字段和数组元素的指令(§2.11.5)也会在操作数栈与其之间传输数据。
还有更多内容。非常有趣的阅读材料。

谢谢,这让我清楚了很多。我一直把它当作一个灰色地带。我也会仔细阅读文档以获得更多的理解。 - Teddy

1

类型信息仅在编译时需要,以生成正确的字节码。字节码指令(类似汇编指令)通常只能作用于一个数据类型。因此,所使用的指令反映了操作数的类型。这对大多数C系列语言都是正确的。

为了看到使用原始类型和动态分配时字节码的区别,让我们来看一个简单的例子。

public static void main (String [] args) {
    int i = 0;
    int j = i + 1;
}

并且生成的字节码如下:

public static void main(java.lang.String[]);
  Code:
     0: iconst_0
     1: istore_1
     2: iload_1
     3: iconst_1
     4: iadd
     5: istore_2
     6: return

所以我们使用istoreiload来存储和加载整数,然后使用iaddi代表整数)将它们相加。
现在看一个例子,使用动态内存分配而不是原始类型:
public static void main (String [] args) {
    Integer i = new Integer(0);
    int j = i + 1;
}

而且还有字节码:

public static void main(java.lang.String[]);
  Code:
     0: new           #2                  // class java/lang/Integer
     3: dup
     4: iconst_0
     5: invokespecial #3                  // Method java/lang/Integer."<init>":(I)V
     8: astore_1
     9: aload_1
    10: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
    13: iconst_1
    14: iadd
    15: istore_2
    16: return

在这个版本中,我们首先需要调用Integer对象的intValue()方法来检索值,然后我们可以通过iadd对其进行操作。
而且,关于数据类型不需要在编译后存储(因为它们被编码在指令本身中,比如“整数存储”istore),请参见jrahhali答案中的引用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接