final 、finally finalize 有什么不同?

final 、finally finalize 有什么不同?

final 可以用来修饰类、方法、变量。
final 修饰的 class 代表不可以继承扩展,final 修饰的方法标识不能被重写(override),final 修饰的方法表示不可以修改。

finally 则是 Java 保证重点代码一定要被执行的一种机制,我们可以使用try-finally或者try-catch-finally来进行类似关闭JDBC连接、保证 unlock锁等动作

finalize 则是基础类 java.lang.Object 的一个方法,他的设计目的是保证对象在被垃圾收集前完成特定资源的回收。 finalize 机制现在已经不推荐使用,并且在JDK9开始被标记为 deprecated

final

推荐使用 final 可以用来修饰类,方法,变量,分别有不应用场景。 Java 核心类库的定义或者源码,有没有发现 java.lang 包下面很多类,想当一部分都被声明为 final class 。在第三方类库的一些基础类也同样如此,这可以有效避免API使用者更改基础功能,某种程度上,这是保证平台安全的必要手段。

  • 使用 final 修饰参数或者变量,也可以清楚地避免意外赋值导致的编程错误,甚至,有人明确推荐将所有方法参数、本地变量、成员变量声明成 final。
  • final变量产生了某种程度的不可变(immutable)的效果,所以,可以用于保护只读数据,尤其是在并发编程中,因为明确地不能再赋值final变量,有利于减少额外的同步开以省去一些防御性拷贝的必要。

final 也许会对性能有好处,比如,利用 final 可能有助于 JVM 方法进行内联,可以改善编译器进行编译的能力。

什么是方法内联?

方法内联:指在即时编译过程中遇到方法调用时,直接编译目标方法的方法体,并替换原方法调用。

函数调用过程:

  1. 首先会有个执行栈,存储它们的局部变量、方法名、动态连接
  2. 当一个方法被调用,一个新的栈帧会被加到栈顶,分配的本地变量和参数会存储在这个栈帧
  3. 跳转到目标方法代码执行
  4. 方法返回的时候,本地方法和参数被销毁,栈顶被移除
  5. 返回原来的地址执行

方法内联的原理就是把调用方函数代码"复制"到调用方函数中。

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

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");
        }
    }

运行结果是什么都不打印

finalize

对于fnalize,我们要明确它是不推荐使用的,业界实践一再证明它不是个好的办法,在Java 9中,甚至明确将Object.fnalize()标记为deprecated!如果没有特别的原因,不要实
现fnalize方法,也不要指望利用它来进行资源回收。
为什么呢?简单说,你无法保证fnalize什么时候执行,执行的是否符合预期。使用不当会影响性能,导致程序死锁、挂起等。
通常来说,利用上面的提到的try-with-resources或者try-fnally机制,是非常好的回收资源的办法。如果确实需要额外处理,可以考虑Java提供的Cleaner机制或者其他替代方
法。接下来,我来介绍更多设计考虑和实践细节。

注意点

final 不是 immutable

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是会在运行时抛出异常的。

  • openjdk.java.net/jeps/269

如何实现不可变类

  • 将class自身声明为fnal,这样别人就不能扩展来绕过限制了。
  • 将所有成员变量定义为private和fnal,并且不要实现setter方法。
  • 通常构造对象时,成员变量使用深度拷贝来初始化,而不是直接赋值,这是一种防御措施,因为你无法确定输入对象不被其他人修改。
  • 如果确实需要实现getter方法,或者其他可能会返回内部状态的方法,使用copy-on-write原则,创建私有的copy

finalize 会有哪些问题

  • 影响GC性能

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进行资源回收。

程序员开发者社区

微信公众号

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: Age of Ai 设计师: meimeiellie
应支付0元
点击重新获取
扫码支付

支付成功即可阅读