编译后 Java 对象引用变量类型会发生什么情况



我不记得在Java字节码中看到过任何引用变量类型的概念。我对类型擦除略知一二,但这个术语似乎与泛型紧密相关,而我的问题是关于一般的对象引用变量。Java对象引用变量类型在编译中幸存下来吗?还是变量类型只是帮助编译器帮助开发人员检查代码是否有意义?如果引用变量类型在编译中幸存下来,它们出现在字节码中的什么位置?

编辑:请允许我在此感谢你所作的宝贵贡献。为了进一步缩小我的想法,我想添加一个例子:

Object o = "foo";

在字节码中,变量 o 及其类型(对象)是否会在任何地方表示并在运行时读取?

是的,字节码也是类型安全的。首先,有一条名为checkcast的字节码指令,每次向下投射时都会使用:

Object obj = "abc";
String s = (String)obj;

翻译成:

aload_1       
checkcast     #3                  // class java/lang/String
astore_2   

其次,invokevirtual和其他人期望给定类型的对象。如果您传递了错误的类型,JVM 将拒绝加载此类。我认为这在Java语言中是无法实现的,所以我做了一些黑客攻击。以下代码:

Integer x = 1;
String s = "abc";
int len = s.length();

翻译成:

   0: iconst_1      
   1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   4: astore_1      
   5: ldc           #3                  // String abc
   7: astore_2      
   8: aload_2       
   9: invokevirtual #4                  // Method java/lang/String.length:()I
  12: istore_3      

请注意加载s局部变量的指令8。使用十六进制编辑器,我用aload_1替换了aload_2,从而尝试在Integer对象(x局部变量)上调用String.length()

$ java Test
Exception in thread "main" java.lang.VerifyError: 
  Bad type on operand stack in method Test.main([Ljava/lang/String;)V at offset 9

只是如果你好奇,如果你禁用类验证,地狱就会崩溃:

$ java -Xverify:none Test
Exception in thread "main" java.lang.NullPointerException
  at java.lang.String.length(String.java:623)
  at Test.main(Test.java:6)

可能会更糟。


最后但并非最不重要的一点是,有很多操作码专用于特定的基元(浮点数、双精度数、整数等)。

JVM加载的所有字节码都经过静态类型检查,以确保其安全。如果没有类型检查,恶意字节码可能只是将 int 视为指针并入侵 VM。

但是,仅仅因为字节码是类型安全的并不意味着它具有显式类型。相反,每个值都有一个由类型推断确定的隐式类型。(从 Java 7 开始,还必须在每个基本块的开头包含指定所有内容的类型的元数据,从而使类型推断任务变得更加容易)。

请注意,虽然所有内容都经过类型检查,但规则比 Java 更宽松。例如,在字节码中,没有布尔值、字节、字符或短局部变量这样的东西。它们都被编译为整数。因此,例如,您可以拥有一个不等于 true 或 false 的布尔值。此外,在对接口调用方法之前,不会检查接口,因此接口变量实际上可以容纳任意对象。最后,热点虚拟机允许您自由混合字节[]和布尔值[]。

字段

类型在字段描述符中编码。

局部变量的类型可以编码到 LocalVariableTable 属性(或泛型类型的 LocalVariableFieldTypeTable 属性)中,并且在面向 Java 6 的类文件中,必须在 StackMapTable 属性中编码。对于较旧的类文件,JVM 将使用类型推断来验证字节码的类型正确性。

因此,虽然您是正确的,字段和变量的类型不是用字节码表示的,但它们在类文件中(至少对于面向 Java 6 或更高版本的类文件)。

相关内容

最新更新