JVM 超级构造函数调用

JVM Super Constructor Calls

在使用 JVM 字节码编译器时,我注意到一些关于构造函数的东西没有多大意义:

每个 Java class 的每个构造函数都调用一个 super 构造函数,甚至是 Object 的直接子 class。这是由 Java 编译器(当它不能在构造函数的开头隐式添加调用时)和字节码验证器强制执行的,如使用自定义 .class 文件的错误所示:

java.lang.VerifyError: Constructor must call super() or this() before return
Exception Details:
  Location:
    dyvil/test/Main.<init>()V @0: return
  Reason:
    Error exists in the bytecode
  Bytecode:
    0000000: b1

在一些随机 class 的 subclass 和字段等的情况下,这是有道理的,因为必须初始化这些字段,但在 sub[=42= 的情况下] Object,这是对 nop 方法的 非常 常见调用(+ 方法调用开销)。这就提出了几个问题:

  1. 为什么 JVM 强制执行此行为?
  2. 超级 class 在子 class 之前加载/初始化是否重要?在调用构造函数之前(在 subclass 初始化时)不能确保这一点吗?
  3. Object 的情况下,class 应该已经在任何给定点加载,那么为什么需要调用它的构造函数?
  4. 这个无用的调用是否被 JIT 编译器优化掉了?

编辑:Objects 构造函数的字节码

public void <init>()
   L0
    LINENUMBER 37 L0
    RETURN
    MAXSTACK = 0
    MAXLOCALS = 1

如您所见,此构造函数根本不执行任何操作。

  1. 除了没有 superclass 之外,Object 和其他的一样都是 class。它有一个必须调用的构造函数。 JVM 规范不排除 Object 在其实例初始化方法中包含指令。
  2. 这是因为静态初始值设定项在 class 加载时执行 (yes, you can avoid that if you load the class manually)。即使它们是在实例化时调用的,也指定层次结构在加载时必须保持一致。有时规格是您唯一的理由。
  3. 加载class和创建实例是两件截然不同的事情。实际上,Hotspot 在 VM 启动时加载了 java.lang 中的所有 classes(如果我没记错的话,我已经很久没看代码了)。另见 1.
  4. 我不知道,但如果我没有忽略任何事情,我想这是可能的。不过,我非常怀疑在任何情况下调用 Object 的构造函数都会花费很多时间。如果您认为可以:测量!

首先,Object.<init>中的单个RETURN并不意味着Object的构造函数是NOP。

Object class 通常是一个 JVM 内部函数,JVM 比它的字节码更了解它。例如,HotSpot 使用 Object.<init> to register finalizers.

Object.<init> 的最佳之处在于 - 它是唯一不可避免地调用 any 实例分配的方法。看看这是一个多么强大的功能:你可以在 Object.<init> 上设置一个断点来一次性拦截所有的构造函数。您还可以使用 Instrumentation API 修改 Object 的构造函数以跟踪所有分配,等等...

关于性能 - 是的,JIT 编译器会在不需要时消除不必要的方法调用,例如当此方法上没有断点时,没有 finalize 等。其他简单的方法也是如此,而不仅仅是 Object.<init>。所以它们没有性能影响。