1、final、finally、finalize的区别
º final修饰类,类不可继承。修饰方法,方法不可重写。修饰变量,变量不可修改。
java.lang包下很多类被声明成final,在第三方类库的一些基础类也同样如此。可有效避免API使用者更改基础功能,是某种程度上保证平台安全的必要手段。
final可保护只读数据,在并发编程中,明确声明不能再赋值的final变量有利于减少额外的同步开销,也可省去一些防御性拷贝的必要。
final可能有助于JVM将方法进行内联,可能改善编译器进行条件编译的能力等。(final对性能的影响一般都没必要考虑)
º finally保证Java某段代码一定被执行,可用来关闭JDBC连接、unlock锁等。如果要关闭资源,推荐Java7新加的try-with-resources语句,通常Java能够更好地处理异常情况,编码量也会少很多。
º finalize是Object的方法,设计的目的是保证对象被垃圾收集前完成特定资源的回收,finalize不推荐使用了,在Java9开始被标记为@Deprecated。
一般不要实现finalize方法,也不要指望用它进行资源回收。因为我们无法保证finalize方法什么时候执行,执行的是否符合预期。使用不当会影响性能,导致死锁、挂起等。
利用try-with-resources或try-finally是非常好的回收资源方法。如果确实要额外处理,可以考虑Java提供的Cleaner机制或其他替代方法。
2、final不是immutable,参考如下代码
final能约束strList的引用不被改变,但strList还是可以add元素。如果希望对象本身不可变,需要相应的类支持不可变的行为。如:List.of(E... elements)返回包含任意数量元素的不可变列表(List.of()是Java9新加的方法)。示例代码中那句add会在运行时抛异常。
Immutable在很多场景下是适用的,Java没有原生的不可变支持,要类实现Immutable需做到:
Ⅰ class自身声明final,不允许别人拓展。
Ⅱ 所有成员变量private和final修饰,并不提供setter方法。
Ⅲ 创建对象用深拷贝,而不是赋值。
Ⅳ 需要才提供getter方法,使用copy-on-writer原则(先备份原来的值,在修改)。
3、Lamdba引用外部局部变量用final修饰的原因
方法执行时会在线程栈中创建一个栈帧,栈帧中包含局部变量表、操作数栈、动态连接等。Lamdba表达式是匿名内部类实现接口的函数,在执行函数时要在线程栈中创建一个新的栈帧,不同栈帧是不能共享局部变量表的。Lamdba表达式栈帧中的变量其实是外部方法栈帧的一个私有拷贝。为保证程序正确性,要求被引用的局部变量被定义成final。如果不定义成final可能会出现数据不一致的情况。
4、finalize真的那么不堪吗?
finalize已被业界证明是非常不好的实践。finalize执行和垃圾收集关联,实现finalize会导致相应对象回收呈数量级上的变慢,大概是40~50倍。
finalize在对象被垃圾收集前调用,JVM要对它进行额外处理,这会成为快速回收的阻碍(可能导致对象经过多个垃圾收集周期才被回收)。finalize拖慢垃圾收集,导致大量对象堆积,也是导致OOM的原因之一。System.runFinalization()让JVM积极一点也许有用,但这是不可预测,不能保证的。
垃圾收集时间的不可预测,可能会极大加剧资源占用。对消耗非常高频的资源,不应指望finalize去做资源释放的主要职责,最多让finalize作最后的盾牌(资源用完应显式释放,或利用资源池来尽量重用)。
5、finalize为什么不可预测
runFinalizer中的Throwable被生吞了,这意味着出现异常或错误,我们得不到任何有效信息。而且Java在finalize阶段也没有好的方式处理任何信息,更加不可预测。
6、有什么机制可替换finalize吗?
Java在逐步使用java.lang.ref.Cleaner替换原有的finalize实现。
Cleaner利用了幻象引用(Phantom Reference),幻象引用是种常见的事后处理机制。利用幻象引用和引用队列,可保证对象被彻底销毁前做些类似资源回收的工作。如:关闭文件描述符(操作系统有限的资源)。
Cleaner比finalize更轻量、更可靠。Cleaner是独立的,有自己的运行线程,可避免意外死锁等问题。
从可预测性来说,Cleaner或幻象引用仍然有限,如果幻象引用堆积也会出现问题,所以Cleaner适合作最后的手段,而不完全依赖Cleaner进行资源回收。
很多第三方库利用幻象引用定制资源收集,如:MySQL-JDBC-Driver中的mysql-connector-j就利用了幻象引用机制。
幻象引用可进行链条式依赖关系的动作,如进行总量控制的场景,保证只有连接被关闭,相应资源被回收,连接池才能创建新的连接。
幻象引用代码不慎添加了对资源的强引用关系会导致循环引用关系,MySQL-JDBC在特定模式会有这种问题导致内存泄漏。幻象机制应避免普通的内部类隐含对外部对象的强引用,那会使外部对象无法进入幻象可达的状态。
往期精彩文章:
单例设计模式之readResolve()方法
Exception和Error的区别
MySQL基本架构与一条SQL语句的执行流程
更多分享,可关注公众号「 桂圆金宝宝 」。