Java核心 -- final + finally + finalize

final

  1. 修饰,代表不可以继承扩展
  2. 修饰变量,代表变量不可以修改
  3. 修饰方法,代表方法不可以重写

实践

  1. 推荐使用final关键字来明确表示代码的语义和逻辑意图
  2. 将方法或类声明为final,明确表示不允许重写或继承
  3. 使用final修饰参数或变量,能够避免意外赋值而导致的编程错误
  4. final变量产生了某种程度的不可变(immutable)的效果,可以用于保护只读数据
  5. 现在JVM足够智能,_final对性能的影响,在大部分情况下,都没有必要考虑_,应用程序更应该关注的是语义

final != immutable

Java目前没有原生的immutable支持

1
2
3
4
5
6
7
8
// final只能约束strList这个引用不可以被赋值,但strList对象本身的行为是不受影响的
final List<String> strList = new ArrayList<>();
strList.add("Hello");
strList.add("World");
// since 9
List<String> unmodifiableStrList = List.of("Hello", "world");
// throw java.lang.UnsupportedOperationException
unmodifiableStrList.add("again");

immutable类

  1. final class
  2. 所以成员变量定义为private final,并且不要实现setter方法
  3. 构造对象时,成员变量使用深度拷贝来初始化
    • 防御编程:因为无法确保输入对象不会被其它线程修改
  4. 如果需要实现getter,使用copy-on-write原则

finally

  1. 保证重点代码一定要被执行的一种机制
  2. try-finallytry-catch-finally
  3. try-with-resources(JDK 7引入)
1
2
3
4
5
6
try {
System.exit(-1);
} finally {
// 不会执行
System.out.println("Print from finally");
}

finalize

  1. java.lang.Object中的一个protected方法
  2. 设计目标:保证对象在被GC前完成特定资源的回收
  3. 不推荐使用,在JDK 9中已经被标记为@Deprecated(since="9")
  4. 无法保证finalize()何时会执行,执行的结果是否符合预期
    • 如果使用不当会影响性能,导致程序死锁、挂起等问题
  5. 一旦实现类非空的finalize方法,会导致对象回收呈现数量级上的变慢(40~50倍)
  6. 实现了finalize方法的对象是特殊公民,JVM需要对它们进行额外的处理
    • finalize本质上成为了快速回收的阻碍者
    • 可能导致对象经过多个GC周期才能被回收
  7. System.runFinalization()同样是不可预测的
  8. 实践中,finalize会拖慢GC,导致大量对象堆积,有可能导致OOM
  9. 对于消耗非常高频的资源,不要指望finalize去承担释放资源的主要职责
    • 推荐做法:资源用完即显式释放,或者利用资源池来复用
  10. 另外,finalize掩盖资源回收时的出错信息
java.lang.ref.Finalizer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Throwable被生吞
private void runFinalizer(JavaLangAccess jla) {
...
try {
Object finalizee = this.get();
if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
jla.invokeFinalize(finalizee);
// Clear stack slot containing this variable, to decrease
// the chances of false retention with a conservative GC
finalizee = null;
}
} catch (Throwable x) { }
super.clear();
}

替代方案 – Cleaner

  1. Java平台逐渐使用java.lang.ref.Cleaner替换掉原有的finalize实现
  2. Cleaner的实现利用了幻象引用(Phantom Reference)
    • 利用幻象引用引用队列,保证对象被销毁之前做一些类似资源回收的工作
  3. Cleanerfinalize更加轻量,更加可靠
  4. 每个Cleaner的操作都是独立的,都有自己的运行线程,可以避免意外死锁等问题
  5. 可预测的角度来判断,Cleaner或者幻象引用改善的程度依然是有限的
    • 由于种种原因导致幻象引用堆积,同样会出现问题
    • Cleaner适合作为最后的保证手段,而不能完全依赖Cleaner进行资源回收
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class CleaningExample implements AutoCloseable {
// A cleaner, preferably one shared within a library
private static final Cleaner cleaner = Cleaner.create();

// State定义为static,为了避免由于普通的内部类隐含对外部对象的强引用,而导致外部对象无法进入幻象可达的状态
static class State implements Runnable {
State() {
// initialize State needed for cleaning action
}

@Override
public void run() {
// cleanup action accessing State, executed at most once
}
}

private final State state;
private final Cleaner.Cleanable cleanable;

public CleaningExample() {
this.state = new State();
this.cleanable = cleaner.register(this, state);
}

@Override
public void close() {
cleanable.clean();
}
}
0%