【发布时间】:2017-10-28 23:55:57
【问题描述】:
我一直在运行一些微基准测试,但遇到了一个奇怪的问题。我正在使用带有默认编译器选项的java version "1.8.0_131"。
给定一个定义
public class JavaState {
public String field = "hello";
public final String finalField = "hello";
}
直接访问field (state.field) 生成
ALOAD 1
GETFIELD JavaState.field : Ljava/lang/String;
但直接访问finalField (state.finalField) 会生成
ALOAD 1
INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
POP
LDC "hello"
Why bytecode calls Object->getClass() at a direct field access 解释说对getClass 的调用只是为了检查state 不是null,但随后编译器已经内联了该字段的值。
我可能合理地期望用不同的字段值替换 JavaState 的更高版本会导致其他代码看到更改而无需重新编译,但是这种内联可以防止这种情况发生。我的基准测试表明,如果以性能的名义完成它是行不通的;至少在我的基准 Raspberry Pi 上,访问 finalField 比访问 field 慢 5-10%。
内联final 字段的值的基本原理是什么?
【问题讨论】:
-
很清楚为什么简单地替换为恒定负载不起作用,并且需要一些东西来验证对象引用不为空。但它仍然是一个有趣的问题,为什么它会被内联 - 为什么用方法调用加上恒定负载来替换字段访问?为什么编译器不只是将字段访问留在那里?这种方式不算什么优化。
-
缺点是 JavaState 类中的更改不会传播到依赖类。由于 HotSpot 是一项古老的技术,因此任何此类优化都可以在 JVM 级别完成,而不会破坏代码的模块化。是时候从源编译器中删除这种“优化”了吗?
-
我已经用 getClass 信息更新了正文 - 我认为问题
Why does the Java compiler inline access to non-static final fields?仍然存在。 -
我会注意到您没有显示代码“直接访问
field”。它住在哪里? -
我的猜测是,在 JIT 上,getClass 上的 invokeVirtual 将在重复时被丢弃。 Raspberry Pi 可能会出现一些效率低下的问题。