引入 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 中,该类Container
被更改为拥有自己的实现paint
:
class Container extends Component
{
public void paint( Graphics g ) {/*...*/}
}
现在,当您有一个直接或间接的子类Container
调用super.paint(g)
并为 1.0.2 编译它时,它会生成一条invokenonvirtual
指令,Component.paint
因为这是第一个拥有此方法的父类。但是,如果您在同样拥有它的 JVM 上使用这个编译的类,Container.paint
它仍然会调用Component.paint
,这不是您所期望的。
另一方面,当您为 1.1 编译该类并在 1.0.2 JVM 上执行它时,它会抛出 AbstractMethodError 或者更有可能导致那个时代的 VM 崩溃。为了避免崩溃,您必须((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 博客上的这篇旧帖子中找到更多相关信息。