我一直在运行一些微基准测试,并遇到了一个奇怪的问题.我正在使用java version "1.8.0_131"默认的编译器选项.
给定一个定义
public class JavaState {
public String field = "hello";
public final String finalField = "hello";
}
Run Code Online (Sandbox Code Playgroud)
field直接访问(state.field)生成
ALOAD 1
GETFIELD JavaState.field : Ljava/lang/String;
Run Code Online (Sandbox Code Playgroud)
但是finalField直接访问(state.finalField)会生成
ALOAD 1
INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
POP
LDC "hello"
Run Code Online (Sandbox Code Playgroud)
为什么在直接字段访问时字节码调用Object-> getClass()解释了调用getClass只是为了检查state不是null,但编译器已经内联了字段的值.
我可能合理地期望用不同的字段值替换更高版本的JavaState会导致其他代码在没有重新编译的情况下看到更改,但是这种内联可以防止这种情况发生.而我的基准测试表明,如果以性能的名义完成,它就无法正常工作; 至少在我的基准测试Raspberry Pi上,访问finalField速度比访问慢5-10%field.
内联价值的理由是什么final?
这可能是Java语言规范要求的,但细节尚不清楚.从第4.12.4节final变量:
常量变量是基本类型或类型String的最终变量,使用常量表达式(第15.28节)初始化.变量是否是常量变量可能对类初始化(第12.4.1节),二进制兼容性(第13.1节,第13.4.9节)和明确赋值(第16节(定义赋值))有影响.
注意,不要求变量为static.然后从第13.1节二进制形式:
必须在编译时将对作为常量变量(§4.12.4)的字段的引用解析为由常量变量的初始值设定项表示的值V.
如果这样的领域是静态的,[...]
如果这样的字段是非静态的,那么除了包含该字段的类之外,二进制文件中的代码中不应该存在对该字段的引用.(它将是一个类而不是一个接口,因为一个接口只有静态字段.)该类应该有代码在实例创建期间将字段的值设置为V(第12.5节).
我不确定你的反编译代码来自哪里.如果它在课外,那么您所看到的是规范要求的.如果它在课堂内,那就不太清楚了.您可以阅读上面引用中的第三段,意味着该字段的唯一代码引用应该在<init>初始化字段的方法中,但实际上并未说明.
第13.4.9节直接解决了您对二进制兼容性的担忧,但似乎明确地将其自身限制为static字段(强调我的):
如果一个字段是一个常量变量(§4.12.4),而且是静态的,那么删除关键字final或更改其值不会破坏与预先存在的二进制文件的兼容性,导致它们不能运行,但它们不会看到任何除非重新编译,否则使用该字段的新值.该结果是决定支持条件编译的副作用(§14.21).(有人可能会认为,如果用法出现在常量表达式中(第15.28节),则不会看到新值.但事实并非如此.事实并非如此;预先存在的二进制文件根本看不到新值.)
要求内联静态常量变量值的另一个原因是由于switch语句.它们是唯一依赖于常量表达式的语句,即switch语句的每个case标签必须是一个常量表达式,其值与每个其他case标签不同.大小写标签通常是对静态常量变量的引用,因此可能不会立即显示所有标签具有不同的值.如果它被证明有在编译时没有重复的标签,然后内联值到类文件,确保有在运行时要么没有重复的标签 - 一个非常理想的性能.
由于非静态常数最终字段不常用,也不是特别有用,因此在编写规范时,它们可能只是滑过裂缝.
| 归档时间: |
|
| 查看次数: |
479 次 |
| 最近记录: |