2012-01-21 103 views
26

当创建新对象时,使用JVM指令invokespecial调用初始化方法(<init>)。该指令的描述建议(但不澄清)关于是否调用超类的构造函数或当前类的构造函数的决定取决于在class文件中设置的ACC_SUPER标志的状态。Java Class文件上ACC_SUPER访问标志的用途是什么?

从Sun JVM规范:

接着,被选择用于调用解析的方法,除非所有的以下条件,则:

  • 的ACC_SUPER标志(参见表4.1,“类访问和属性修饰符“)被设置为当前类。

- Sourceinvokespecial操作码定义)

的ACC_SUPER标志的设定指示哪个其invokespecial指令两个替代语义的Java虚拟机是表达; ACC_SUPER标志的存在是为了向后兼容由Sun编译器为Java编程语言编译的代码。 Java虚拟机的所有新实现都应该实现本规范中记录的invokespecial的语义。 Java虚拟机指令集中的所有新编译器都应设置ACC_SUPER标志。 Sun的较早的编译器会生成ACC_SUPER未设置的ClassFile标志。 Sun的较早的Java虚拟机实现如果设置了该标志,则忽略该标志。

- SourceClassFile格式)

定义指出该标志是用于与旧的编译器的向后兼容性。然而,它与Sun's older Java virtual machine implementations ignore the flag if it is set.

是否矛盾仍然与invokespecial操作码一起使用?从我所知道的看来,它似乎没有任何目的,我找不到一个资源来暗示它曾经做过。

谢谢。

+1

这不是一个答案,但要理解为什么使用该标志,我认为您还需要了解该标志在JVM版本1规范下的含义 - 请参阅http://java.sun.com/docs/books /jvms/first_edition/html/Instructions2.doc7.html#invokespecial –

+0

并排,我看不出任何区别。 – Jivings

回答

33

ACC_SUPER被引入来解决调用超级方法的问题。 ACC_SUPER标志标记了一个为编译操作码183指令的语义而编译的类。它的用途与类文件版本号相似,因为它允许JVM检测是否为该指令的较旧或较新的语义编译了一个类。 Java 1.0.2未设置并忽略ACC_SUPER,而Java 1.1和更高版本始终设置ACC_SUPER。

在Java 1.1之前,带有操作码183的字节码指令现在称为invokespecial,称为invokenonvirtual,并且具有部分不同的规格。无论何时必须在没有虚拟方法查找的情况下调用实例方法,都会使用它。私有方法,实例初始化程序(构造函数)以及在super上实现方法调用的情况就是这种情况。但后一种情况导致了不断发展的类库的问题。

字节代码中的方法引用(CONSTANT_Methodref_info)不仅定义了方法的名称,参数和返回类型,还定义了它所属的类。操作码183获得这样的方法引用参数,并且意在直接从指定的类中调用引用的方法,而无需进一步查找。在调用super的情况下,编译器负责解析实现此方法的最近的超类,并为其生成对字节代码的引用。

从Java 1.1开始,它已被更改为基本忽略CONSTANT_Methodref_info中引用的类,而是使用JVM中的给定方法名称和签名来查找最接近的超级方法。这通常是在类被加载时执行,或者在执行指令或第一次编译JIT之前执行。

下面是为什么需要进行此更改的示例。在Java 1.0.2的AWT类容器和组件被定义是这样的:

class Component 
{ 
    public void paint(Graphics g) {} 
} 

class Container extends Component 
{ 
    // inherits paint from Component but doesn't override it 
} 

在Java 1.1类Conatiner改为有它自己的执行paint

class Container extends Component 
{ 
    public void paint(Graphics g) {/*...*/} 
} 

现在,当你有Container的一个直接或间接的子类,它在super.paint(g)上拨打电话并编译为1.0.2,它生成invokenonvirtual指令Component.paint,因为这是第一个拥有此方法的父级。但是如果你在一个也有Container.paint的JVM上使用这个编译过的类,它仍然会调用Component.paint这不是你所期望的。另一方面,当您为1.1编译类并在1.0.2 JVM上执行它时,它会抛出AbstractMethodError或更可能导致该时代的VM崩溃。为避免崩溃,您必须编写((Component)super).paint(g)并使用1.1编译器进行编译,以便在任一VM中获得所需的行为。这将设置ACC_SUPER,但仍会生成调用Component.paint的指令。 1.0.2虚拟机会忽略ACC_SUPER并直接调用Component.paint,这很好,而1.1 VM会发现ACC_SUPER集,因此执行查找本身,即使字节码方法引用是Component.paint,也会引起Container.paint

你可以在this old post on the ikvm.net weblog找到更多关于它的信息。

+0

有趣的帖子。那么1.1之后的所有编译器都会设置标志,从而使JVM在解析方法时检查'super'层次结构?这样做,而不是使用从堆栈弹出的'objectref'? – Jivings

+0

这是一个非常好的答案。我现在明白'invokespecial'是如何与标志一起工作的。但为什么他们不只是弃用旧的方法呢? – Jivings

+13

从Java 7u13安全更新开始,JVM不再支持ACC_SUPER标志的缺失。这是因为这里描述的问题:http://weblog.ikvm.net/PermaLink.aspx?guid=23cced47-ccdb-460d-acc9-ce16154ab6a5 –