final 可以用来修饰类、方法、变量。final 修饰的 class 代表不可以继承扩展,final 修饰的方法标识不能被重写(override),final 修饰的方法表示不可以修改。
finally 则是 Java 保证重点代码一定要被执行的一种机制,我们可以使用try-finally或者try-catch-finally来进行类似关闭JDBC连接、保证 unlock锁等动作
finalize 则是基础类 java.lang.Object 的一个方法,他的设计目的是保证对象在被垃圾收集前完成特定资源的回收。 finalize 机制现在已经不推荐使用,并且在JDK9开始被标记为 deprecated
推荐使用 final 可以用来修饰类,方法,变量,分别有不应用场景。Java 核心类库的定义或者源码,有没有发现 java.lang 包下面很多类,想当一部分都被声明为 final class 。在第三方类库的一些基础类也同样如此,这可以有效避免API使用者更改基础功能,某种程度上,这是保证平台安全的必要手段。
final 也许会对性能有好处,比如,利用 final 可能有助于 JVM 方法进行内联,可以改善编译器进行编译的能力。
方法内联:指在即时编译过程中遇到方法调用时,直接编译目标方法的方法体,并替换原方法调用。
方法内联的原理就是把调用方函数代码"复制"到调用方函数中。
private int add2(int x1 , int x2 , int x3 , int x4) {
return add1(x1 , x2) + add1(x3,x4);
}
private int add1(int x1 , int x2) {
return x1 + x2;
}
方法内联会翻译成如下:
private int add2(int x1 , int x2 , int x3 , int x4) {
//return add1(x1 , x2) + add1(x3,x4);
return x1 + x2 + x3 + x4;
}
final 声明的方法,就是同意编译器将针对该方法的调用都转化为内联调用,因此有可能对性能有好处,《Java编程思想》 中有说明。
finally 主要是使要知道怎么用就可以。
public static void main(String[] args) {
try {
// do something
return ;
} finally{
System.out.println("Print From Finally");
}
}
运行结果:
Print From Finally
测试代码
public static void main(String[] args) {
try {
// do something
System.exit(0);
} finally{
System.out.println("Print From Finally");
}
}
运行结果是什么都不打印
对于fnalize,我们要明确它是不推荐使用的,业界实践一再证明它不是个好的办法,在Java 9中,甚至明确将Object.fnalize()标记为deprecated!如果没有特别的原因,不要实 现fnalize方法,也不要指望利用它来进行资源回收。为什么呢?简单说,你无法保证fnalize什么时候执行,执行的是否符合预期。使用不当会影响性能,导致程序死锁、挂起等。通常来说,利用上面的提到的try-with-resources或者try-fnally机制,是非常好的回收资源的办法。如果确实需要额外处理,可以考虑Java提供的Cleaner机制或者其他替代方 法。接下来,我来介绍更多设计考虑和实践细节。
final List<String> strList = new ArrayList<>();
strList.add("Hello");
strList.add("World");
List<String> unmodifyList = List.of("hello","World");
unmodifyList.add("again");
final只能约束strList这个引用不可以被赋值,但是strList对象行为不被fnal影响,添加元素等操作是完全正常的。如果我们真的希望对象本身是不可变的,那么需要相应的类支持不可变的行为。在上面这个例子中, List.of方法创建的本身就是不可变List,最后那句add是会在运行时抛出异常的。
finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法。finalize的执行是和垃圾收集关联在一起的,一旦实现了非空的 finalize方法,就会导致相应对象回收呈现数量级上的变慢,有人专门做过 benchmark,大概是40~50倍的下降。
要确保回收资源就是因为资源都是有限的,垃圾收集时间的不可预测,可能会极大加剧资源占用。这意味着对于消耗非常高频的资源,因为fnalize拖慢垃圾收集,导致大量对象堆积,也是一种典型的导致OOM的原因。
java.lang.ref.Fonalizer
private void runFinalizer(JavaLangAccess jla) {
synchronized (this) {
if (hasBeenFinalized()) return;
remove();
}
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();
}
这里可以看到 Throwsable 被生吞了,一旦出现异常,你将得不到任何有效的信息。
Java平台目前在逐步使用 java. lang ref Cleaner来替换掉原有的 finalize实现。Cleaner 的实现利用了幻象引用( Phantom Reference),这是一种常见的所谓 post-morten清理机制。我会在后面的专栏系统介绍Java的各种引用,利用幻象引用和引用队列,我们可以保证对象被彻底销毀前做一些类似资源回收的工作,比如关闭文件描述符(操作系统有限的资源),它比 finalizer更加轻量、更加可靠。
package com.logicbig.example;
import java.lang.ref.Cleaner;
public class CleanerExample {
public static void main(String[] args) {
Cleaner cleaner = Cleaner.create();
for (int i = 0; i < 10; i++) {
String id = Integer.toString(i);
MyObject myObject = new MyObject(id);
cleaner.register(myObject, new CleanerRunnable(id));
}
//myObjects are not reachable anymore
//do some other memory intensive work
for (int i = 1; i <= 10000; i++) {
int[] a = new int[10000];
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
}
}
private static class CleanerRunnable implements Runnable {
private String id;
public CleanerRunnable(String id) {
this.id = id;
}
@Override
public void run() {
System.out.printf("MyObject with id %s, is gc'ed%n", id);
}
}
}
从可预测性的角度来判断, Cleaner或者幻象引用改善的程度仍然是有限的,如果由于种种原因导致幻象引用堆积,同样会出现问题。所以,Cleaner适合作为一种最后的保 证手段,而不是完全依赖Cleaner进行资源回收。