为什么流不关闭会导致内存泄漏

引言

经常有人告诉你流用完要记得关,不然会导致内存泄漏,但你是否考虑过下面这些问题:

  1. 为什么流不关会导致内存泄漏?
  2. JVM不是有垃圾回收机制吗?这些引用我用完不就变垃圾了为什么不会被回收呢?
  3. 流未关闭除了导致内存泄漏?是否还会引发别的问题?

这对这些问题,本文就再次对IO流底层工作工作原理展开探讨。

问题复现

代码演示

我们首先来一段示例代码,每次请求时就会创建1w个文件输入流,创建完成后并没有关闭,后续我们会通过压测工具请求这个接口。

@RequestMapping("noClose")public String noClose() throws FileNotFoundException {//每次请求进来就创建1w次输入文件输入流for (int i = 0; i < 10000; i++) {openFileStream();}return "success";}private static void openFileStream() throws FileNotFoundException {InputStream is = new FileInputStream("data.txt");}

为了更快看到效果,我们调整堆内存为50m:

-Xmx50m
问题定位

随后我们通过jmeter进行接口压测,不久后问题就出现了:

Exception in thread "RMI TCP Connection(idle)" java.lang.OutOfMemoryError: GC overhead limit exceeded
.....
Caused by: java.lang.OutOfMemoryError: GC overhead limit exceededException in thread "RMI TCP Connection(idle)" java.lang.OutOfMemoryError: GC overhead limit exceeded
Exception in thread "RMI TCP Connection(idle)" java.lang.OutOfMemoryError: GC overhead limit exceeded
Exception in thread "RMI TCP Connection(idle)" java.lang.OutOfMemoryError: GC overhead limit exceeded
原因分析

对此我们通过jps定位进程号,然后将内存信息导出:

jmap -dump:live,format=b,file=xxxx.hprof pid

通过mat将导出的xxxx.hprof打开,可以看到排名前3的几个类中包含了File相关,内存泄漏问题很明显是出在我们对文件的操作上。

先来说说排名第二的FileDescriptor,每个FileInputStrean内部都会维护一个FileDescriptorFileDescriptor可视为一个文件描述符,是打开一个文件的句柄。

public
class FileInputStream extends InputStream
{/* File Descriptor - handle to the open file */private final FileDescriptor fd;//略
}

对应的我们上文构造方法的调用如下:

  1. 将传入的文件名生成一个File对象,并调用另一个构造方法。
  2. 另一个构造方法进行安全以及文件有效性检查。
  3. 创建文件描述符,并让文件描述符和当前流进行关联,确保后续可以关闭。
  4. 通过open调用操作系统的open函数打开文件并获得文件句柄,此时我们的流就和系统资源关联起来了。
 public FileInputStream(String name) throws FileNotFoundException {this(name != null ? new File(name) : null);}public FileInputStream(File file) throws FileNotFoundException {//安全性检查String name = (file != null ? file.getPath() : null);SecurityManager security = System.getSecurityManager();if (security != null) {security.checkRead(name);}//文件有效性检查if (name == null) {throw new NullPointerException();}if (file.isInvalid()) {throw new FileNotFoundException("Invalid file path");}//创建文件描述符,并获得文件句柄并设置到fd上fd = new FileDescriptor();fd.attach(this);path = name;open(name);}

所以,当我们使用完流之后不将流关闭,FileDescriptor将会一直持有着操作系统资源,所以当JVM进行垃圾回收时,因为文件资源还没有释放,这些类就无法及时被及时GC。

为了验证流是否持有资源,我们也可以在上述代码执行完成后,尝试在计算机上删除一下文件看看,最终结果会如下图所示,可以看到文件始终无法删除,很明显它被FileDescriptor所有持有。

由此我们得出,当IO流未关闭时,FileDescriptor将一直持有系统资源,所以GC进行垃圾回收时,无法将FileDescriptor对象及时回收,流不关闭不仅会导致内存泄漏,还会导致对系统资源持续占用,影响其他进程对系统资源的使用。

再来看看排名第一的Finalizer,因为是和垃圾回收相关,我们可以直接通过Finalizer类来定位问题,所以我们通过点击with outgoing references查看其引用了那些类:

可以看到该类内部引用了FileInputStream(占用内存排名第3的类),而FileInputStream又引用了FileDescriptor(排名第二的类)。

我们在FileInputStream会看到,它重写了finalize方法,从代码上可以看出该方法会对没有及时回收的FileDescriptor进行流释放和系统资源归还。

protected void finalize() throws IOException {if ((fd != null) &&  (fd != FileDescriptor.in)) {close();}}

查阅资料笔者发现,重写finalize方法的类将会被Finalizer所引用,正因被Finalizer所引用,所以即使我们使用完成并退出函数后,进行GC时这些对象并不会被回收。

只有当GC完成之后,JVM才会将这些仅仅被Finalizer引用的类标记出来,并存放到ReferenceQueue这个队列中,直到被Finalizer线程发现并调用finalize后,以本文为例finalize即释放文件句柄和系统资源,Finalizer线程会将我们的FileInputStreamFileDescriptor对应的其从Finalizer引用中移除,下一次GC时即可被回收。

因为Finalizer线程优先级非常低,所以这些垃圾被回收的频率是非常低的,这也就是为什么我们会在内存快照中看到大量Finalizer指向的类没有被及时回收。
因为我们手动关闭的流的缘故,导致大量的FileDescriptor类持有文件流和系统资源,使得FileDescriptor无法被GC回收,需要借助Finalizer线程调用finalize释放系统资源后才具备被GC的资格,由于Finalizer线程优先级极低,流的创建速度远远大于回收速度,最终就导致堆内存无法及时释放出现内存泄漏。

解决方案

解决方案也很简单,及时关闭流就好了,而且jdk7也为我们提供了try-with-resource,语法简洁需多。

protected void finalize() throws IOException {if ((fd != null) &&  (fd != FileDescriptor.in)) {close();}}

更进一步

其实某些类我们操作完成后,可以不关闭流,例如:ByteArrayOutputStreamByteArrayInputStream,我们查看它的close方法,可以看到是空实现的,原因很简单,它们操作数据流时是在内存中操作字节的,并不会持有操作系统文件资源,当然了,为了统一开发习惯,我们还是建议读者操作流时,调用一下close。

public void close() throws IOException {}

小结

当IO流不关闭时,可能会导致以下对象无法被回收:

  1. FileInputStream 或其他输入流对象:如果你没有关闭 FileInputStream 对象,它会一直持有底层文件的句柄,这可能会导致文件资源无法释放。这样的对象将无法被垃圾回收器回收。
  2. FileOutputStream 或其他输出流对象:类似地,如果你没有关闭 FileOutputStream 对象,它可能会持有底层文件的句柄,并且可能导致写入缓冲区中的数据无法刷新到磁盘。这可能会导致资源泄漏和数据丢失。
  3. Socket 或其他网络连接相关的对象:如果你没有关闭 Socket 或其他网络连接相关的对象,它们可能会保持与远程主机的连接状态,这会导致网络资源无法释放,这些对象将无法被垃圾回收器回收,同样也可能导致端口号占用导致其他线程无法使用该端口的情况。
  4. BufferedReaderBufferedWriter 或其他缓冲流对象:如果你没有关闭这些缓冲流对象,它们可能会持有底层的输入流或输出流对象,并且可能会导致数据未能刷新或缓冲区数据未能清空。这可能会导致资源泄漏和数据丢失。

需要注意的是,即使没有显式地关闭这些对象,某些情况下它们可能会在垃圾回收器执行时被自动回收。但是,这取决于具体的垃圾回收算法和实现,所以我们不能依赖这种行为。正确的做法是在使用完这些对象后,显式地调用它们的 close() 方法来关闭流并释放相关资源,以防止资源泄漏和数据丢失。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/608125.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

node的下载、安装、配置

下载&#xff1a; 官网下载&#xff1a;Node.js 左右两个都可以&#xff1a; 安装&#xff1a; 打开cmd&#xff1a; 输入以下指令&#xff0c;如果出现版本号说明安装成功 node -v npm -v 配置&#xff1a; 1、新建文件夹&#xff1a;node_cache和node_global作为npm“缓…

前端实现搜索功能

最近遇到一个需求,用户在输入框输入关键字之后,点击搜索按钮后进行搜索,如下图,选中的数据在下面,上面展现的是搜索后的数据,现在选中了2条数据: 当用户输入KET后点击搜索,搜出的结果有16条,勾选全选选中后,将选中的16条的数据加到之前已选的2条数据里,于是此时已选…

重磅!大模型框架 LangChain 首个稳定版本终于来了!

著名的大模型智能体工具&#xff0c;现在有大版本更新了。 不知不觉&#xff0c;LangChain 已经问世一年了。作为一个开源框架&#xff0c;LangChain 提供了构建基于大模型的 AI 应用所需的模块和工具&#xff0c;大大降低了 AI 应用开发的门槛&#xff0c;使得任何人都可以基于…

oracle角色管理

常用角色 CONNECT,RESOURCE,DBA,EXP_FULL_DATABASE,IMP_FULL_DATABASE 1角色可以自定义&#xff0c;语法与创建用户一样 CREATE role role1 IDENTIFIED by 123; 2授权权限给角色 --自定义角色 CREATE role role1 IDENTIFIED by 123; --授权权限给角色 GRANT create view, …

AI人工智能从业人员《自然语言及语音处理设计开发工程师》证书专项培训(第二期)通知!

工业和信息化部电子工业标准化研究院联合北京龙腾亚太教育咨询有限公司和北京龙腾智元信息技术有限公司于2024年1月成功在京举办AI人工智能从业人员《自然语言及语音处理设计开发工程师》证书专项培训第一期课程&#xff0c;所有学员成功通过考试。介于学员的良好反应&#xff…

设置flex布局的元素,其子元素宽度和超过其本身时,其宽度值未被撑起问题

如图父元素main-content设置了display:flex. 里面包含了不确定个数的子元素&#xff0c;子元素样式为&#xff1a; flex: 1; min-width: 240px;现在想获取父元素的宽度&#xff0c;发现无论子元素的个数为多少&#xff0c;父元素的宽度都是一样的大小&#xff0c;并没有被子元…

Docker与微服务实战(基础篇)

Docker与微服务实战&#xff08;基础篇&#xff09; 1、Docker简介2、Docker安装步骤1.Centos7及以上的版本2.卸载旧版本3.yum安装gcc相关4.安装需要的软件包5.设置stable镜像仓库【国内aliyun】6.更新yum软件包索引--以后安装更快捷7.安装Docker-Ce8.启动Docker9.测试10.卸载1…

Socket closed 异常解决方案:如何解决 JMeter 压测中的问题

问题描述 JMeter 压测时会报 java.net.SocketException: Socket closed java.net.SocketException: Socket closed at java.net.PlainSocketImpl.socketConnect(Native Method) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) at java.ne…

Temu、Shopee、Lazada等跨境流量如何提升?买家号如何批量养号?

现在在temu、Lazada、shopee等跨境电商平台开店的商家越来越多。如果商家想让商店的产品得到更多的展示&#xff0c;流量是必不可少的&#xff0c;平台的流量入口主要有几个板块。 让我们谈谈temu、Lazada、shopee搜索流量如何提升&#xff0c;有什么方法。 有两种方法可以在短…

usb转32串口方案

方案结构图 使用usb hub芯片扩展4路usb然后再一分八路串口 USB hub 选择hub芯片注意事项&#xff1a; 目前市场上多数的USB 2.0 Hub芯片,只有内建一个Transaction Translators(STT)&#xff0c;因此 当Hub接收到如Full Speed的装置进入时&#xff0c;12Mbps的「单一」信道…

使用Moonbuilders Academy平台,学习DApp开发

Moonbeam团队于2022年宣布开放Moonbuilders Academy。这是一套以开发为中心的异步学习课程&#xff0c;用于学习如何在Moonbeam上构建跨链DApp。 如何从官网进入平台&#xff1f; 点击http://moonbeam.network 鼠标移动至 “Builders”&#xff0c;在Resources下方选择“Moo…

2024腾讯爱奇艺首发片单,谁能率先拿下开年爆款?

刚进入2024年&#xff0c;头部长视频平台就开启了新一轮“内卷”。 腾讯和爱奇艺不约而同地在2024年的第一天发布了新剧片单&#xff0c;多部高质量精品大剧蓄势待发&#xff0c;点燃了观众和市场的期待。 2023年之争已经落下帷幕&#xff0c;爱奇艺凭借大爆剧《狂飙》拔得头…

Next City 数都上海应用创新大赛结果公布,子虔科技获奖

12月16日&#xff0c;以“应变求机 以数谋新”为主题的上海城市数字化转型体验周举办。作为上海城市数字化转型年终重磅活动&#xff0c;上海市人民政府副秘书长庄木弟&#xff0c;市经济和信息化工作党委书记程鹏&#xff0c;杨浦区委副书记、区长周海鹰&#xff0c;市经济和信…

Apache ActiveMQ 远程代码执行漏洞分析

漏洞简介 Apache ActiveMQ官方发布新版本&#xff0c;修复了一个远程代码执行漏洞&#xff0c;攻击者可构造恶意请求通过Apache ActiveMQ的61616端口发送恶意数据导致远程代码执行&#xff0c;从而完全控制Apache ActiveMQ服务器。 影响版本 Apache ActiveMQ 5.18.0 before …

统信UOS虚拟机安装VirtualBox扩展使用USB功能

为什么要安装VirtualBox扩展包&#xff1f; 安装 Oracle VM VirtualBox 扩展包的原因是&#xff0c;它提供了对 USB 2.0、USB 3.0、远程桌面协议 VRDP&#xff08;VirtualBox Remote Desktop Protocol&#xff09;等实用功能的支持&#xff0c;以增强 VirtualBox 的功能。这些…

HarmonyOS 应用开发学习笔记 ets组件生命周期

HarmoryOS Ability页面的生命周期 Component自定义组件 ets组件生命周期官放文档 本文讲解 ets组件的生命周期&#xff0c;在此之前大家可以先去了解Ability的生命周期&#xff0c;这两个生命周期有有一定的关联性 在开始之前&#xff0c;我们先明确自定义组件和页面的关系&…

RPA财务机器人在厦门市海沧医院财务管理流程优化汇总的应用

目前国内外研究人员对于RPA机器人在财务管理流程优化领域中的应用研究层出不穷&#xff0c;但现有研究成果主要集中在财务业务单一领域&#xff0c;缺乏财务管理整体流程一体化管控的研究。RPA机器人的功能绝非单一的财务业务处理&#xff0c;无论从自身技术发展&#xff0c;或…

常见的Latex公式所用到的内容汇总

行内公式 f ( x ) a b f(x)ab f(x)ab 左右各加一个$&#xff0c;即为行内公式 $ f(x) ab $行间公式 $$ f(x) ab $$f ( x ) a b f(x)ab f(x)ab 手动编号 $$ f(x) a - b \tag{1.1} $$f ( x ) a − b (1.1) f(x)a-b \tag{1.1} f(x)a−b(1.1) 简单运算 -*/以及阿拉伯…

为什么大型服务器要用 Linux 系统?

为什么大型服务器要用 Linux 系统&#xff1f; 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「Linux的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff…

创新百喻,综合性思维和分析性思维

创新百喻&#xff0c;综合性思维和分析性思维 不知道您注意没有&#xff0c;在创新中&#xff0c;人们的思维方式是不一样的&#xff0c;有综合性思维和分析性思维之分。总的来说&#xff0c;综合性思维适合创造原来没有的事物&#xff0c;而分析性思维擅长改进和提高&#xf…