001-基础知识专题-关键字和接口-2、final
1. final 的内存语义
- 在构造函数内对一个 final 域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。也就是说只有将对象实例化完成后,才能将对象引用赋值给变量。
- 初次读一个包含 final 域的对象的引用,与随后初次读这个 final 域,这两个操作之间不能重排序。也就是下面示例的 4 和 5 不能重排序。
- 当 final 域为引用类型时,在构造函数内对一个 final 引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
下面通过代码在说明一下:
1 |
|
2. final 语义在处理器中的实现
- 会要求编译器在 final 域的写之后,构造函数 return 之前插入一个 StoreStore 障屏。
- 读 final 域的重排序规则要求编译器在读 final 域的操作前面插入一个 LoadLoad 屏障。
3. 内联优化
https://juejin.cn/post/7107909896804237319#heading-1
我们先看直接声明就初始化这种情况:
1 |
|
编译再反编译一下:
1 |
|
可以看到这个 getA()
的反编译结果,编译器已经知道了 a=7
,并且由于它是一个 final 变量,不会变,所以直接写死编译进去了。相当于直接把 return a
替换成了 return 7
。
1 |
|
这其实是一个编译器的优化,专业的称呼叫“内联优化”。其实不只是 final 变量会被内联优化。一个方法也有可能被内联优化,特别是热点方法。JIT 大部分的优化都是在内联的基础上进行的,方法内联是即时编译器中非常重要的一环。
一般来说,内联的方法越多,生成代码的执行效率越高。但是对于即时编译器来说,内联的方法越多,编译时间也就越长,程序达到峰值性能的时刻也就比较晚。有一些参数可以控制方法是否被内联:
回到最开始的问题,这种能被编译器内联优化的 final 变量,是会在编译成字节码的时候,就赋值了,所以在类加载的准备阶段,不会给这个变量初始化为默认值。
4. 与 static 搭配使用
static final 用来修饰成员变量和成员方法,可简单理解为“全局常量”!
对于变量,表示一旦给值就不可修改,并且通过类名可以访问。
对于方法,表示不可覆盖,并且可以通过类名直接访问。
5. 参考与感谢
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Taylor!
评论