-
final关键字:
- 可以将方法或者类声明为final,明确告诉不可以被修改。如Java类库的一些基础类中相当一部分被声明为final class,这样做可以有效避免API使用者更改基础功能,同样也是保证平台安全的必要手段。
- 使用final修饰参数或者变量,也可以清楚地避免意外赋值导致的边城错误,甚至,有人明确推荐将所有方法参数、本地变量、成员变量声明成final。
- Final变量产生了某种程度的不可变(immutable)的效果,所以可以用于保护只读数据,尤其是在并发编程中,因为明确地不能再赋值final变量,有利于减少额外的同步开销,也可以省去一些防御性拷贝的必要。
- Final 也许会有性能的好处, 可以利用final帮助JVM将方法进行内联,可以改善编译器进行条件编译的能力等等。但这些仅仅是基于假设得出的结论,比如JVM(Hotspot)判断内联未必依赖final的提示,以及还有很多类似的,final字段对性能的影响,大部分情况可以忽略。
- 在实际开发场景中,除非特别考虑性能环节,不然最好不要指望这种小技巧带来的所谓性能好处,程序本身就应该体现它的语义目的。
-
finally关键字:
- 明确知道如何使用finally
- 需要关闭的链接等资源,可以使用Java7中添加的try-with-resources语句,通常情况下Java平台本身可以更好的处理异常情况,编码量也要少很多
-
finalize关键字:
- 明确知道已经不推荐使用,并且Java9将Object.finalize()标记为deprecated,
- 不要指望利用finalize来资源回收,因为我们无法保证finalize什么时候执行,执行的是否符合预期,使用不当还会影响性能,导致程序死锁、挂起等。
-
finalize的问题?真的那么不好用?
- 为啥导致不好用?finalize的执行是和垃圾收集关联在一起的,一旦实现了非空的finalize方法,就会导致相应对象回收呈现数量级上的变慢,benchmark显示大概会有40~50倍的下降。
- finalize被设计成灾对象被垃圾回收前调用,就是意味着实现了finalize的方法的对象是“特殊公民”,JVM要怼它进行额外处理。finalize本质上成为了快速回收的阻碍者,可能导致你的对象经过多个垃圾收集周期才能被收回
- System.runFinalization()告诉JVM积极一点,是不是就可以满足快速垃圾回收,但这种方式本身是不可预测情况发生的,并且不能保证,所以在本质上不能指望这么操作以解决问题,现实环境中,由于finalize拖慢垃圾收集,导致大量对象堆积,也是一种典型的导致OOM的原因
- 不要指望利用finalize来资源回收,因为我们无法保证finalize什么时候执行,执行的是否符合预期,使用不当还会影响性能,导致程序死锁、挂起等。
- final可以用来修饰类、方法、变量,分别有不同的意义,final修饰的class代表不可以继承扩展,final的变量是不可以修改的,而final的方法也是不可以重写的(override)
- finally则是Java保证重点代码一定要被执行的一种机制。我们可以使用try-finally或者try-catch-finally来进行类似关闭JDBC连接、保证unlock锁等操作
- finalize是基础类Java.lang.Object的一个方法,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收。finalize机制现在已经不推荐使用,并且在JDK9开始被标记deprecated
- 问题:从概念上理解语法规范上的理解就是以上说法,但从全面的只是体系出发分析围绕性能、并发、对象生命周期或垃圾收集基本过程等方面的理解
-
扩展情况
- 注意1.final不是immutable(不可变)
final List<String> strList = new ArrayList<>();
strList.add(“hello”);
strList.add(“world”);
List<String> unmodifiableStrList = List.of(“hello”, “world”);
unmodifiableStrList.add(“agin”);
- final只能约束strList这个引用不可以被赋值,但是strList对象行为不被final影响,添加元素等操作完全正常的。如果我们真的希望对象本身是不可变的,那么需要相应的类支持不可变的行为。在上面这个例子中,List.of方法创建的本身就是不可变的List,最后那句add是会在运行时抛出异常的。
- Immutable在很多场景是很好的选择,Java目前没有原生的不可变支持,如果要实现immutable的类,我们需要满足一下条件:
- 将class自身声明为final,这样别人就不能扩展来绕过限制了
- 将所有成员变量定义为private和final,且不要实现setter方法
- 通常构造对象时,成员变量使用深度拷贝来初始化,而不是直接赋值,这是一种防御措施,因为这个环节无法确定输入对象不被其他人修改。
- 如果确实需要实现getter方法,或者其他可能会返回内部状态的方法,使用copy-on-write原则,创建私有的copy
- 关于setter、getter方法,建议最好是确定需要再生成
-
finalize的替代方案:
- Cleaner的实现利用了幻象引用(PhantomReference)这是一种常见的post-mortem 清理机制,利用幻象引用和引用队列,我们可以保证对象被彻底销毁前做一些类似资源回收的工作,比如关闭文件描述符,这种方式比finalize更轻量、更加可靠一点
- 每个Cleaner的操作都是独立的,有自己的运行线程,这样也就避免意外死锁等问题
- 虽然如此,但Cleaner或者幻象引用改善的成仍然有限的,如果由于种种原因导致幻象引用堆积,同样会出现问题。所以Cleaner适合作为一种最后的保证手段,不能完全依赖
- 常见的使用幻象引用机制有MYSQL JDBC driver之一的mysql-connector-j,幻象引用也可以进行类似链条式依赖关系的动作,比如,进行总量控制的场景,保证只有连接被关闭,相应资源被回收,连接池才能创建新的连接
总结Summary
- 一般情况,利用try-with-resourcs 或者try-finally机制,是非常好的回收资源的办法。如果特殊情况需要额外处理,可以考虑Java提供的Cleaner机制或者其他替代方法
- 回收资源是因为资源都是有限的,垃圾收集时间的不可预测,可能会机打家具资源占用。这意味着对于消耗非常高频的资源,不能通过finalize去承担资源释放的主要职责,最多让finalize作为最后的守门员,况且它已经被实践中暴露出问题,所以建议资源用完即显式释放,或者利用资源池来尽量重用。
-
不要在finally中使用return语句