inputstream 占用内存吗_GZIPInputStream 流未关闭引起的内存泄漏问题

近日线上一个项目总是时隔1周发生OOM自动重启,问题很明显内存泄漏了。。。

使用jmap查看一下线上服务堆使用情况,实例最多的前10个类

110125 instances of class [C

108705 instances of class java.lang.String

88066 instances of class

java.util.concurrent.ConcurrentHashMap$Node

79224 instances of class java.lang.Object

52984 instances of class [B

48482 instances of class java.lang.ref.Finalizer

39684 instances of class java.util.zip.Inflater

39684 instances of class java.util.zip.ZStreamRef

28168 instances of class [Ljava.lang.Object;

26576 instances of class java.util.HashMap$Node

看到这个类排名第一反应就是GZIP相关的操作可能有问题,那么我们目光聚集到代码上吧

public static String unZip(String str) throws IOException {

ByteArrayOutputStream out = new ByteArrayOutputStream();

byte[] bytes = Base64.getDecoder().decode(str);

ByteArrayInputStream in = new ByteArrayInputStream(bytes);

GZIPInputStream gzip = new GZIPInputStream(in);

byte[] buffer = new byte[256];

int n = 0;

while ((n = gzip.read(buffer)) >= 0) {

out.write(buffer, 0, n);

}

return out.toString(CODE);

}

这段代码是当时想要使用GZIP做解压缩从网上抄来了,当时只是用单测验证了一下这段代码的正确性,就上线了。

出现了内存泄漏问题之后,回过头来反思这段代码发现这里使用了3个流ByteArrayOutputStream,ByteArrayInputStream,GZIPInputStream。

重点是这三个流在代码结束之后都没有关闭!!!

依次点开三个流的close()方法看了下

ByteArrayOutputStream,ByteArrayInputStream这两个流的close()方法其实是空的,说明这两个流其实关闭与否都没有关系。

GZIPInputStream的close()方法

public void close() throws IOException {

if (!closed) {

super.close();

eos = true;

closed = true;

}

}

看到这个方法后,具体怎么关闭的其实不那么重要了,重要的是说明了这个流是需要关闭的

现在我们再看看内存泄漏的具体原因是什么吧,我们依次点开GZIPInputStream的构造方法

public GZIPInputStream(InputStream in, int size) throws IOException {

super(in, new Inflater(true), size); //看到堆内大量实例Inflater了

usesDefaultInflater = true;

readHeader(in);

}

点开Inflater的构造方法

public Inflater(boolean nowrap) {

zsRef = new ZStreamRef(init(nowrap)); //c++方法init

}

这个init方法使用了C++ calloc申请内存,这部分内存是无法被Java GC回收的,这就导致我们的服务可用堆内存越来越小,最后程序OOM Crash重启

修改这段代码很简单,所有流使用完毕之后关闭就好了,最终代码如下

private static String unZip(String str) throws IOException {

try (ByteArrayOutputStream out = new ByteArrayOutputStream();

ByteArrayInputStream in = new ByteArrayInputStream(Base64.getDecoder().decode(str));

GZIPInputStream gzip = new GZIPInputStream(in)) {

byte[] buffer = new byte[256];

int n = 0;

while ((n = gzip.read(buffer)) >= 0) {

out.write(buffer, 0, n);

}

return out.toString(CODE);

}

}

虽然ByteArrayOutputStream,ByteArrayInputStream这两个流的close()方法为空,无需关闭。但是从这个线上问题得出的反思,任何流,使用完毕之后一定要注意养成关闭的好习惯。(除非需要复用流)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值