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 方法的 非常 常见调用(+ 方法调用开销)。这就提出了几个问题:
- 为什么 JVM 强制执行此行为?
- 超级 class 在子 class 之前加载/初始化是否重要?在调用构造函数之前(在 subclass 初始化时)不能确保这一点吗?
- 在
Object
的情况下,class 应该已经在任何给定点加载,那么为什么需要调用它的构造函数?
- 这个无用的调用是否被 JIT 编译器优化掉了?
编辑:Object
s 构造函数的字节码
public void <init>()
L0
LINENUMBER 37 L0
RETURN
MAXSTACK = 0
MAXLOCALS = 1
如您所见,此构造函数根本不执行任何操作。
- 除了没有 superclass 之外,
Object
和其他的一样都是 class。它有一个必须调用的构造函数。 JVM 规范不排除 Object
在其实例初始化方法中包含指令。
- 这是因为静态初始值设定项在 class 加载时执行 (yes, you can avoid that if you load the class manually)。即使它们是在实例化时调用的,也指定层次结构在加载时必须保持一致。有时规格是您唯一的理由。
- 加载class和创建实例是两件截然不同的事情。实际上,Hotspot 在 VM 启动时加载了
java.lang
中的所有 classes(如果我没记错的话,我已经很久没看代码了)。另见 1.
- 我不知道,但如果我没有忽略任何事情,我想这是可能的。不过,我非常怀疑在任何情况下调用
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>
。所以它们没有性能影响。
在使用 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 方法的 非常 常见调用(+ 方法调用开销)。这就提出了几个问题:
- 为什么 JVM 强制执行此行为?
- 超级 class 在子 class 之前加载/初始化是否重要?在调用构造函数之前(在 subclass 初始化时)不能确保这一点吗?
- 在
Object
的情况下,class 应该已经在任何给定点加载,那么为什么需要调用它的构造函数? - 这个无用的调用是否被 JIT 编译器优化掉了?
编辑:Object
s 构造函数的字节码
public void <init>()
L0
LINENUMBER 37 L0
RETURN
MAXSTACK = 0
MAXLOCALS = 1
如您所见,此构造函数根本不执行任何操作。
- 除了没有 superclass 之外,
Object
和其他的一样都是 class。它有一个必须调用的构造函数。 JVM 规范不排除Object
在其实例初始化方法中包含指令。 - 这是因为静态初始值设定项在 class 加载时执行 (yes, you can avoid that if you load the class manually)。即使它们是在实例化时调用的,也指定层次结构在加载时必须保持一致。有时规格是您唯一的理由。
- 加载class和创建实例是两件截然不同的事情。实际上,Hotspot 在 VM 启动时加载了
java.lang
中的所有 classes(如果我没记错的话,我已经很久没看代码了)。另见 1. - 我不知道,但如果我没有忽略任何事情,我想这是可能的。不过,我非常怀疑在任何情况下调用
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>
。所以它们没有性能影响。