Java类文件上的ACC_SUPER访问标志的目的是什么?
invokespecial
JVM指令用于在创建新对象时调用初始化方法( )。 该指令的描述建议(但不澄清)关于是否调用超类的构造函数或当前类的构造函数的决定取决于
class
文件中设置的ACC_SUPER
标志的状态。
来自Sun JVM规范:
接下来,选择已解析的方法进行调用,除非满足以下所有条件:
- 为当前类设置ACC_SUPER标志(参见表4.1“类访问和属性修饰符”)。
– Source ( invokespecial
opcode definition)
ACC_SUPER标志的设置指示Java虚拟机要表达的其invokespecial指令的两个备选语义中的哪一个; 存在ACC_SUPER标志,以便向后兼容Sun的旧编译器为Java编程语言编译的代码。 Java虚拟机的所有新实现都应该实现本规范中记录的invokespecial的语义。 Java虚拟机指令集的所有新编译器都应设置ACC_SUPER标志。 Sun的旧编译器生成了Class_Sile标志,并且未设置ACC_SUPER。 Sun的旧Java虚拟机实现会在设置时忽略该标志。
– 来源 ( ClassFile
格式)
该定义指出该标志是为了与旧编译器向后兼容。 然而,它继续与Sun's older Java virtual machine implementations ignore the flag if it is set.
相矛盾, Sun's older Java virtual machine implementations ignore the flag if it is set.
该标志是否仍与invokespecial
操作码一起使用? 从我所知,它似乎没有任何目的,我找不到建议它曾经做过的资源。
谢谢。
引入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类Container和Component以这种方式定义:
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编译它时,它为Component.paint
生成了一个invokenonvirtual
指令,因为这是第一个拥有这个方法的父类。 但是如果你在一个也有Container.paint
的JVM上使用这个编译类,它仍然会调用Component.paint
,这不是你所期望的。
另一方面,当您编译1.1的类并在1.0.2 JVM上执行它时,它会抛出一个AbstractMethodError,或者更可能是那个时代的虚拟机崩溃。 为了避免崩溃,您必须编写((Component)super).paint(g)
并使用1.1编译器对其进行编译,以在任一VM中获得所需的行为。 这将设置ACC_SUPER,但仍然生成调用Component.paint
的指令。 1.0.2 VM会忽略ACC_SUPER并直接调用Component.paint
,这是好的,而1.1 VM会找到ACC_SUPER设置,因此执行查找本身会使它调用Container.paint
即使字节码方法引用是Component.paint
。
您可以在ikvm.net博客上的这篇旧post中找到更多相关信息。