对字节码和对象的澄清

我正在写一个字节码指导员。 现在,我试图找出在对象存在的情况下如何做到这一点。 我想对我在JVMS中阅读的两行(第4.9.4节)做一些澄清:

1)“validation者在初始化之前拒绝使用新对象的代码。”

我的问题是,“使用”在这里意味着什么? 我猜这意味着:将它作为方法属性传递,在其上调用GETFIELDPUTFIELD ,或者调用它上面的任何实例方法。 他们的其他禁止用途? 我相信它可以遵循其他指令,如DUPLOADSTORE

2)“在该方法调用myClass的另一个实例初始化方法或其直接超类之前,该方法可以对此执行的唯一操作是分配在myClass中声明的字段。”

这意味着在方法中,在调用另一个之前允许GETFIELD和PUTFIELD。 但是,在Java中,在调用super()this()之前对实例字段执行任何操作都会导致编译错误。 有人可以澄清一下吗?

3)我还有一个问题。 对象引用何时初始化,因此可以自由使用? 通过阅读JVMS,我想出了一个对象是否被初始化的答案,取决于每种方法。 在某个时间点,可以为方法初始化对象,但不能为另一个方法初始化对象。 具体来说,当该方法调用的返回时,对象的方法被初始化。

例如,考虑main()方法创建了一个对象并调用了 ,然后调用了超类的 。 从super()返回后,该对象现在被认为是由初始化的,但尚未为main()初始化。 这是否意味着,在super()之后的 ,我可以将对象作为参数传递给方法,甚至在返回到main()之前。

有人可以证实这整个分析是真的吗? 感谢您的时间。

ps:我实际上已经向Sun论坛发布了相同的问题,但回复。 我希望我能在这里有更多的运气。 谢谢。

更新

首先感谢您的答案和时间。 虽然我没有得到一个明确的答案(我有很多问题,其中一些有点模糊),但你的答案和例子,以及随后的实验,对于我更深入地了解JVM如何工作非常有用。

我发现的主要问题是Verifier的行为因不同的实现和版本而不同(这使得字节码操作的工作变得更加复杂)。 问题在于不符合JVMS,或者validation者的开发人员缺乏文档,或者JVMS在validation者的区域中有一些微妙的模糊性。

最后一件事,SO Rocks !!! 我在官方的Sun JVM规范论坛上发布了同样的问题,直到现在我仍然没有回答。

我建议您下载OpenJDK源代码的副本,并查看validation程序实际检查的内容。 如果不出意外,这可能有助于您了解JMV规范所说的内容。

(但是,@ Joachim是对的。依赖于validation器实现的function而不是规范所说的是相当危险的。)

“validation程序拒绝在初始化之前使用新对象的代码。”

在字节码validation中,由于validation器在链接时工作,因此推断出方法的局部变量的类型。 方法参数的类型是已知的,因为它们位于类文件中的方法签名中。 其他局部变量的类型是未知的并且是推断的,因此我假设上述语句中的“使用”与此相关。

编辑: JVMS第4.9.4节的内容如下:

类myClass的实例初始化方法(第3.9节 )将新的未初始化对象视为本地变量0中的此参数。在此方法之前调用myClass的另一个实例初始化方法或其直接超类,该方法可以执行的唯一操作这是分配在myClass中声明的字段。

上述语句中的字段分配是在分配对象的内存时,将实例变量“初始”初始化为默认初始值(如int为0,float为0.0f等)。 当虚拟机调用对象上的实例初始化方法(构造函数)时,实例变量还会再进行一次“正确”初始化。

John Horstmann提供的链接有助于澄清事情。 所以这些陈述并不成立。 “这个DOESNOT意味着在方法中,在调用另一个之前允许使用getfieldputfield 。” getfieldputfield指令用于访问(和更改)类(或类的实例)的实例变量(字段)。 这只有在初始化实例变量(字段)时才会发生。“

来自JVMS:

除了从Object类的构造函数派生的实例初始化方法之外,每个实例初始化方法(第3.9节)必须在访问实例成员之前调用此实例的另一个实例初始化方法或其直接超类super的实例初始化方法。 但是,可以在调用任何实例初始化方法之前分配在当前类中声明的实例字段。

当Java虚拟机隐式或显式创建类的新实例时,它首先在堆上分配内存以保存对象的实例变量。 内存被分配给在对象的类及其所有超类中声明的所有变量,包括隐藏的实例变量。 只要虚拟机为新对象预留了堆内存,它就会立即将实例变量初始化为默认初始值。 一旦虚拟机为新对象分配了内存并将实例变量初始化为默认值,就可以为实例变量提供正确的初始值。 Java虚拟机使用两种技术来执行此操作,具体取决于是否由于clone()调用而创建了对象。 如果由于clone()而正在创建对象,则虚拟机会将要克隆的对象的实例变量的值复制到新对象中。 否则,虚拟机将在对象上调用实例初始化方法。 实例初始化方法将对象的实例变量初始化为其正确的初始值。 只有在这之后才能使用getfieldputfield

java编译器为它编译的每个类生成至少一个实例初始化方法(构造函数)。 如果类没有显式声明构造函数,则编译器生成一个默认的no-arg构造函数,它只调用超类no-arg构造函数。 并且正确地在调用super()this()之前对实例字段执行任何操作都会导致编译错误。

方法可以包含三种代码:另一个方法的调用,实现任何实例变量初始化器的代码,以及构造函数体的代码。 如果构造函数以同一个类中的另一个构造函数的显式调用开始( this()调用),则其相应的方法将由两部分组成:

  • 调用相同类的方法
  • 实现相应构造函数体的字节码

如果构造函数不以this()调用开头并且该类不是Object,则方法将包含三个组件:

  • 调用超类方法
  • 任何实例变量初始值设定项的字节码
  • 实现相应构造函数体的字节码

如果构造函数不以this()调用开头且类是Object(并且Object没有超类),那么它的方法不能以超类方法调用开始。 如果构造函数以显式调用超类构造函数( super()调用)开头,则其方法将调用相应的超类方法。

我认为这回答了你的第一个和第二个问题。

更新:

例如,

  class Demo { int somint; Demo() //first constructor { this(5); //some other stuff.. } Demo(int i) //second constructor { this.somint = i; //some other stuff...... } Demo(int i, int j) //third constructor { super(); //other stuffff...... } } 

inheritance编译器(javac)中上述三个构造函数的字节码:

 Demo(); Code: Stack=2, Locals=1, Args_size=1 0: aload_0 1: iconst_5 2: invokespecial #1; //Method "":(I)V 5: return Demo(int); Code: Stack=2, Locals=2, Args_size=2 0: aload_0 1: invokespecial #2; //Method java/lang/Object."":()V 4: aload_0 5: iload_1 6: putfield #3; //Field somint:I 9: return Demo(int, int); Code: Stack=1, Locals=3, Args_size=3 0: aload_0 1: invokespecial #2; //Method java/lang/Object."":()V 4: return 

在第一个构造函数中, 方法首先调用相同类的方法,然后执行相应构造函数的主体。 因为构造函数以this()开头,所以它对应的方法不包含用于初始化实例变量的字节码。

在第二个构造函数中,构造函数的方法具有

  • 超类方法,即调用超类构造函数(无arg方法),编译器默认生成它,因为没有找到显式的super()作为第一个语句。
  • 用于初始化实例变量someint的字节码。
  • 构造函数体中其余东西的字节码。

与java语言指定的相反,在字节码级别,可以在调用超类构造函数之前访问构造函数中的类的字段。 以下代码使用asm库来创建这样的类:

 package asmconstructortest; import java.io.FileOutputStream; import org.objectweb.asm.*; import org.objectweb.asm.util.CheckClassAdapter; import static org.objectweb.asm.Opcodes.*; public class Main { public static void main(String[] args) throws Exception { //ASMifierClassVisitor.main(new String[]{"/Temp/Source/asmconstructortest/build/classes/asmconstructortest/Test.class"}); ClassWriter cw = new ClassWriter(0); CheckClassAdapter ca = new CheckClassAdapter(cw); ca.visit(V1_5, ACC_PUBLIC + ACC_SUPER, "asmconstructortest/Test2", null, "java/lang/Object", null); { FieldVisitor fv = ca.visitField(ACC_PUBLIC, "property", "I", null, null); fv.visitEnd(); } { MethodVisitor mv = ca.visitMethod(ACC_PUBLIC, "", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitInsn(ICONST_1); mv.visitFieldInsn(PUTFIELD, "asmconstructortest/Test2", "property", "I"); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V"); mv.visitInsn(RETURN); mv.visitMaxs(2, 1); mv.visitEnd(); } ca.visitEnd(); FileOutputStream out = new FileOutputStream("/Temp/Source/asmconstructortest/build/classes/asmconstructortest/Test2.class"); out.write(cw.toByteArray()); out.close(); } } 

实例化此类工作正常,没有任何validation错误:

 package asmconstructortest; public class Main2 { public static void main(String[] args) { Test2 test2 = new Test2(); System.out.println(test2.property); } }