前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >final 、finally finalize 有什么不同?

final 、finally finalize 有什么不同?

作者头像
王小明_HIT
发布2020-05-11 11:21:32
7590
发布2020-05-11 11:21:32
举报
文章被收录于专栏:程序员奇点程序员奇点

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. 返回原来的地址执行

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

代码语言:javascript
复制
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;
}

方法内联会翻译成如下:

代码语言:javascript
复制
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 主要是使要知道怎么用就可以。

代码语言:javascript
复制
 public static void main(String[] args) {
        try {
            // do something
            return ;
        } finally{
            System.out.println("Print From Finally");
        }
    }

运行结果:

代码语言:javascript
复制
Print From Finally

测试代码

代码语言:javascript
复制
    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

代码语言:javascript
复制
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

代码语言:javascript
复制
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更加轻量、更加可靠。

代码语言:javascript
复制
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进行资源回收。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员奇点 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • final 、finally finalize 有什么不同?
    • final
      • 什么是方法内联?
      • 函数调用过程:
    • finally
      • finalize
        • 注意点
          • final 不是 immutable
          • 如何实现不可变类
        • finalize 会有哪些问题
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档