浅析Java中的资源关闭

13 篇文章 0 订阅

内存是计算机很宝贵的资源,我们在使用资源时如果不关闭打开的资源,就有可能导致内存泄露的风险,下面浅析下Java中几种常见的资源关闭方案

先定义一个资源类表示需要关闭的资源

public class MyResource implements Closeable {
    @Override
    public void close() throws IOException {
        System.out.println("MyResource的close方法被调用!");
    }
}

一、直接在try块中关闭,如下代码

public class FinallyTest {
    public static void main(String[] args) {
        try {
            MyResource myResource = new MyResource();
            System.out.println("抛出异常前...");
            int i = 1 / 0;
            myResource.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

我们可以看到输出的结果是

其实我们可以看到,当抛出异常后try块中异常后续的代码就不会被执行,如当我们执行代码int i = 1 / 0时,这句代码肯定会抛出异常,当抛出异常后try块中的代码就不会被执行了,即后面的myRosource.close()方法就不会被调用。如果代码中存在大量的资源调用,但是抛出了异常,就会导致内存泄露的风险,其实我们从字节码文件中也可以知道结果字节码如下图所示。

二、接下来我们看看使用try{...}catch{....}finally{...}的形式,接下来我们把代码改为如下形式,把调用close的代码移动到finally中。

public class FinallyTest {
    public static void main(String[] args) throws IOException {
        MyResource myResource = null;
        try {
            myResource = new MyResource();
            System.out.println("抛出异常前...");
            int i = 1 / 0;
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if (myResource != null) {
                myResource.close();
            }
        }
    }
}

输出结果如下,此时我们从输出结果中可以看出,不管try块中是否抛出异常,finally最终都会被执行

接下来我们从字节码的层面看看调用机制。其实我们可以看到,不管是走哪一个分支,最终都会执行finally的语句

三、try-with-resources 但是从JDK1.7开始,有新的方式。我们看接口AutoCloseable,是从JDK1.7开始提供的

/*
 * @author Josh Bloch
 * @since 1.7
 */
public interface AutoCloseable {
    void close() throws Exception;
}

我们定义如下测试代码

public class FinallyTest {
    public static void main(String[] args) throws IOException {
        try (MyResource myResource = new MyResource()) {
            System.out.println("抛出异常前...");
            int i = 1 / 0;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

执行结果如下,我们不需要显示调用close方法,close会被调用,执行结果如下。

那java是如何实现的呢?我们反编译字节码看看结果,这就是try...catch...finally的形式。try-catch-resource这种写法只是一个语法糖。

但是细心的朋友可能发现var2.addSuppressed(var11)这个是什么鬼?我们去看看Throwable中方法addSuppressed的注释,如下所示。可以看到,这个方法也是从JDK1.7开始的。省略了部分注释,其实就是为了避免异常覆盖。

/**
     * Appends the specified exception to the exceptions that were
     * suppressed in order to deliver this exception. This method is
     * thread-safe and typically called (automatically and implicitly)
     * by the {@code try}-with-resources statement.
     * @since 1.7
     */
    public final synchronized void addSuppressed(Throwable exception) {
        if (exception == this)
            throw new IllegalArgumentException(SELF_SUPPRESSION_MESSAGE, exception);

        if (exception == null)
            throw new NullPointerException(NULL_CAUSE_MESSAGE);

        if (suppressedExceptions == null) // Suppressed exceptions not recorded
            return;

        if (suppressedExceptions == SUPPRESSED_SENTINEL)
            suppressedExceptions = new ArrayList<>(1);

        suppressedExceptions.add(exception);
    }

接下来我们把MyResource的代码改一下,让close方法抛出异常。如下所示。

public class MyResource implements Closeable {
    @Override
    public void close() throws IOException {
        System.out.println("MyResource的close方法被调用!");
        throw new RuntimeException("我这里抛出异常啦....");
    }
}

然后我们用传统的try{...}catch{...}finally{...}看看输出结果是什么样的。

public static void main(String[] args) throws Exception {
        MyResource myResource = new MyResource();
        try {
            System.out.println("抛出异常前....");
            int i = 1 / 0;
        } catch (Exception ex) {
            throw ex;
        } finally {
            if (myResource != null) {
                myResource.close();
            }
        }
    }

输出结果如下。

发现了吗?堆栈里面没有int i = 1 / 0 ,所产生的异常。异常被屏蔽了,针对这个问题,addSuppressed就可以派上用场啦。更多addSuppressed的使用可参考

接下来我们看看try...with...Resource...的形式的解决方案。代码如下

public static void main(String[] args) throws Exception {
        try (MyResource myResource = new MyResource()) {
            System.out.println("抛出异常前....");
            int i = 1 / 0;
        } catch (Exception ex) {
            throw ex;
        }
    }

输出结果如下:

看到了吗?被append后面啦。

个人学习记录

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Java,有两种内部类:静态内部类和非静态内部类。它们的主要区别在于访问方式和用途。 静态内部类是一个独立的类,但它是作为宿主类的一个静态成员存在的。它可以访问宿主类的静态成员,但不能直接访问宿主类的非静态成员。另外,静态内部类的实例化不依赖于宿主类的实例化,可以直接通过宿主类访问或使用。静态内部类通常用于将一个类嵌套在另一个类,并与其它外部类共享。 非静态内部类是一个依赖于宿主类实例的类,它只能在宿主类的实例被实例化。非静态内部类可以直接访问宿主类的成员,包括静态和非静态成员。非静态内部类的实例化必须依赖于宿主类的实例,并通过宿主类的实例访问或使用。非静态内部类通常用于充当宿主类的辅助类,以提供更多特定于宿主类实例的功能。 总结起来,静态内部类可以看作是宿主类的静态成员,独立于宿主类的实例存在,并且可以直接使用宿主类的静态成员;非静态内部类是宿主类的一部分,依赖于宿主类的实例存在,并且可以直接使用宿主类的所有成员。 在实际应用,选择使用静态内部类还是非静态内部类取决于具体需求。如果一个类不依赖于宿主类的实例,且能够独立使用,那么可以使用静态内部类;如果一个类需要依赖于宿主类的实例,并且需要访问宿主类的成员,那么就需要使用非静态内部类。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值