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()方法为空,无需关闭。但是从这个线上问题得出的反思,任何流,使用完毕之后一定要注意养成关闭的好习惯。(除非需要复用流)
原文地址:https://www.cnblogs.com/migoo/p/11752667.html
- Spring Cloud实战小贴士:Ribbon的饥饿加载(eager-load)模式
- android常用接口(二)
- Spring Cloud实战小贴士:Zuul的饥饿加载(eager-load)使用
- RxAndroid完全教程
- 全能型反汇编引擎 – Capstone-Engine
- Hijack攻击揭秘
- 都在说微服务,那么微服务的反模式和陷阱是什么(二)
- Spring Boot 2.0 - WebFlux framework
- Spring Cloud构建微服务架构:服务网关(路由配置)【Dalston版】
- SpringCloud实战小贴士:Zuul的路径匹配
- 程序员你为什么这么累【续】:编码习惯之参数校验和国际化规范
- 程序员你为什么这么累【续】:编码习惯-函数编写建议
- 那些年,我们一起碰到过的骗局
- Spring Security (五) 动手实现一个IP_Login
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法