java 代码扫描_静态代码扫描 (四)——Java 资源关闭研究

这是静态代码扫描系列文章的第四篇,前三篇文章介绍了如何使用 PMD 和 Findbugs 自定义规则。

我们火线团队最近一直在研究 java 资源关闭的检查规则,发现市面上开源的工具针对资源关闭的检测都存在一定不足,同时也无法满足我们业务的需求。所以我们针对资源关闭进行了深度的研究,取得了一些不错的进展,但是过程的艰辛也远超了我们的预料。现在就跟大家聊聊我们的心路历程,从为什么开始。

1. 为什么要手动关闭 Java 资源对象?

首先解释 Java 的资源对象,它主要包括 IO 对象,数据库连接对象。比如常见的 InputStream、OutputStream、Reader、Writer、Connection、Statement、ResultSet、Socket 等等,先代码列举一个示例:

FileInputStream f = new FileInputStream("sample.txt");

f.close();//f对象即需要手动关闭的资源对象

上述代码中 f 对象即需要手动关闭的资源对象。

如果类似的资源对象没有及时的手动关闭,这个对象就会一直占据内存,当这样的对象越来越多,那内存被占用的就会越来越多,久而久之就可能造成 OutOfMemory,俗称内存溢出。

41fd83977db98796280672600b42ef1f.png

这时应该有人会问,Java 不是有自己的垃圾回收机制 GC 么?不是可以自动回收么?

这个问题问的好,我也一度非常困惑。

首先我们先了解一下 GC 的原理:

在 Java 中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾。JVM 的一个系统级线程会自动释放该内存块。垃圾回收意味着程序不再需要的对象是"无用信息",这些信息将被丢弃。当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用。

0a8ff0df659862f84c5779b9ff2efa5d.png

首先 GC 只能回收内存。至于各种 stream 之类,他们下边一般还开启了各种其他的系统资源,比如文件,比如输入输出设备(键盘/屏幕等),等等。而这些设备第一是不能自动关闭(因为谁知道你程序要用它到什么时候啊),另一个系统内数量有限(比如键盘/屏幕同一时间只有一个)。最后,文件和数据库连接之类的东西还存在读写锁定的问题。这些都导致用户必须手动处理这些资源的开启和关闭。

其次为了 “避免” 程序员忘了自己释放那些资源,Java 提供了 finalizer、PhantomReference 之类的机制来让程序员向 GC 注册 “自动回调释放资源” 的功能。但 GC 回调它们的时机不确定,所以只应该作为最后手段来使用,主要手段还是自己关闭最好。

PS:关于 GC 其实有很多的知识可以深度挖掘,比如各种回收算法,finalize() 方法等等,大家感兴趣的话可以自行搜索研究,我就不班门弄斧了。

2. 怎样正确的手动关闭 Java 资源对象?

先说一种最常见的关闭方式,在 finally 中进行关闭:

FileInputStream f;

try{

f= new FileInputStream("sample.txt");

//something that uses f and sometimes throws an exception

}

catch(IOException ex){

/* Handle it somehow */

}

finally{

f.close();

}

这里在 finally 中进行资源对象关闭属于 Best Practice。因为即使对象 f 在使用的过程中出现异常,也能保证程序不会跳过后续的关闭操作。

特别注意,自从 Java1.7 开始,支持了 try-with-resources 写法,即将资源对象声明的过程放在 try() 的括号里面,这样 java 在资源对象使用完成之后会自动关闭。

try (

FileOutputStream fileOutputStream = new FileOutputStream("E:\\A.txt");

BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);

DataOutputStream out = new DataOutputStream(bufferedOutputStream)

)

{

out.write(data1);

} catch (Exception e) {

// TODO: handle exception

}

另外还有一些第三方库提供了一些统一的关闭处理方法,例如

import org.apache.commons.io.IOUtils;

public static void main(String[] args) throws Exception{

FileOutputStream fileOutputStream = null;

BufferedOutputStream bufferedOutputStream=null;

DataOutputStream out=null;

byte[] data1 = "这个例子测试文件写".getBytes("GB2312");

try {

fileOutputStream = new FileOutputStream("E:\\A.txt");

bufferedOutputStream = new BufferedOutputStream(fileOutputStream);

out = new DataOutputStream(bufferedOutputStream);

out.write(data1);

} catch (Exception e) {

// TODO: handle exception

} finally {

IOUtils.closeQuietly(out);

}

}

这个 apache 提供的 IOUtils 类库可以通过 IOUtils.closeQuietly(e) 的形式关闭资源对象,实际内部实现依然是调用.close() 方法。内部实现代码如下:

public static void closeQuietly(final Closeable closeable) {

337 try {

338 if (closeable != null) {

339 closeable.close();

340 }

341 } catch (final IOException ioe) {

342 // ignore

343 }

344 }

以上就是手动关闭 Java 资源对象的几种推荐写法,希望对大家有所帮助。

为防止篇幅过长,这只是系列文章的第一篇,我将在下一篇继续讲述在判断资源关闭时,有哪些不为人知的特殊情况需要考虑。

敬请期待。

参考文献:

360Qtest 团队公众号

关注公众号,第一时间收到我们推送的新文章~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值