java 压缩 内存_Java压缩流GZIPStream导致的内存泄露

我们来聊聊GZIPOutputStream 和 GZIPInputStream, 如果不关闭流会引起的问题,以及GZIPStream申请和释放堆外内存的流程, Let's do it!

引子

在我的工程里面又一个工具类 ZipHelper 用来压缩和解压 String

最近服务出现了占用swap空间的问题,初步定位为内存泄漏,最后通过分析定位到是 Native 方法Java_java_util_zip_Inflater_init一直在申请内存(关于分析方法可以查阅这篇博客内存泄露分析实战)但是没有释放,很有可能就是流没有关闭造成的,而这部分代码最大的问题就是没有在finally里面去关闭流,于是乎我打算改造这部分代码,利用 try-with-resource 语法糖,然后代码就被修改成了这样:

是不是顺眼多了呐,可是这样的代码可以压缩的,在解压的时候会报错。一开始我以为是解压的代码出现了问题,最后才发现是因为压缩的时候没有成功压缩,导致解压的时候无法解压。报以下错误

好好的代码怎么会突然压缩失败,后来发现的问题是在GZIPOutputStream中,在close()方法中会主动调用finish()方法。

在下面的方法中才会将压缩后的数据输出到输入流,由于原来的代码会调用 close()方法,从而间接调用了 finish() 方法。那我我们的try-with-resource到底出了什么问题,其实问题就在于执行close()的时间。

try-with-resource 执行时机和条件

try-with-resource 是在 JDK7 中新增加的语法糖(其实就是抄的C#),用来自动执行流的关闭操作,只要该类实现了AutoCloseable的close()方法。

实现了这个接口之后,我们可以将会在try代码块执行结束之后自动关闭流

由于在GZIPOutputStream执行了finish()方法或者close()方法之后才会真正的将压缩后的数据写入流,在上文我改造的代码中并没有首先执行finish()方法,而是直接在try代码块执行完之后关闭了流 GZIPOutputStream, 由于close()方法执行在out.toString("ISO-8859-1")之后,因此压缩并没有真正的被执行,然而对于ZipHelper.compress()方法并没有感知,而是返回了没有压缩成功的字符串,从而造成在解压的时候报错。

为什么会引起的堆外内存泄漏

通过最开始的代码我们可以看出,在没有发生异常的情况下,compress()方法是可以正常的关闭流的,所以内存泄露的根源应该是在uncompress()方法,通过跟踪GZIPInputStream的构造函数和close()应该很快就能找到答案。

下面是申请堆外内存和释放堆外内存的过程调用图,可以对比代码参考

format,png

堆外内存调用释放流程图

由于篇幅的原因就不将JDK源码注释一同贴上来了,感兴趣的同学可以按图索骥,找到对应的注释。

openJDK 中 JVM 关于这个本地方法的实现

参考

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值