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

引言

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

  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 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
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() 方法来关闭流并释放相关资源,以防止资源泄漏和数据丢失。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

分布式(5)

目录 22.什么是Paxos算法&#xff1f;如何实现&#xff1f; 24.全局唯一ID有哪些实现方案&#xff1f; 25.数据库方式实现方案&#xff1f;有什么缺陷&#xff1f; 22.什么是Paxos算法&#xff1f;如何实现&#xff1f; Paxos算法是Lamport宗师提出的一种基于消息传递的分布…

【2024系统架构设计】 系统架构设计师第二版-通信系统架构设计理论与实践

目录 一 通信系统网络架构 二 网络构建的关键技术 三 网络构建和设计方法 四 案例分析 注:本节内容可作为知识储备,做一个基本的了解即可。

sonarqube配置本地扫描代码

一、本地maven设置setting文件&#xff1a; 1&#xff09;添加pluginGroup <pluginGroups><pluginGroup>org.sonarsource.scanner.maven</pluginGroup></pluginGroups> 2&#xff09;添加profile&#xff1a; <profile><id>sonar</i…

抓包神技--DPDK

DPDK&#xff0c;全称Data Plane Development Kit&#xff0c;是一个高性能的数据包处理工具集。估计有不少朋友使用过或者之前了解过&#xff0c;它通过绕过Linux内核协议栈&#xff0c;直接在用户空间进行数据包处理&#xff0c;大大提高了数据包处理的效率和吞吐量。 DPDK主…

Google Breakpad使用方法

源码下载地址&#xff1a;https://chromium.googlesource.com/breakpad/breakpad 依赖头文件下载地址&#xff1a; https://chromium.googlesource.com/linux-syscall-support Breakpad由三个主要组件&#xff1a; client 是一个库, 以library的形式内置在应用中&#xff0c…

可碧教你C++——位图

本章节是哈希的延申 可碧教你C——哈希http://t.csdnimg.cn/3R8TU 一文详解C——哈希 位图 位图是基于哈希表的原理产生的一种新的container——bitset 基于哈希映射的原理&#xff0c;我们在查找的时候&#xff0c;可以直接去定址到元素的具体位置&#xff0c;然后直接访问该…

池化层解析:简单易懂理解 PyTorch 中的核心组件

目录 torch.nn详解 nn.MaxPool1d nn.MaxPool2d nn.MaxPool3d nn.MaxUnpool1d nn.MaxUnpool2d nn.MaxUnpool3d nn.AvgPool1d nn.AvgPool2d nn.AvgPool3d nn.FractionalMaxPool2d nn.FractionalMaxPool3d nn.LPPool1d nn.LPPool2d nn.AdaptiveMaxPool1d nn.Adapt…

Springboot+RocketMQ通过事务消息优雅的实现订单支付功能

目录 1. 事务消息 1.1 RocketMQ事务消息的原理 1.2 RocketMQ订单支付功能设计 1. 事务消息 RocketMQ的事务消息&#xff0c;是指发送消息事件和其他事件需要同时成功或同时失败。比如银行转账&#xff0c; A银行的某账户要转一万元到B银行的某账户。A银行发送“B银行账户增加…

VirtualBox安装OpenEuler

VirtualBox安装OpenEuler 下载地址 virtualbox下载地址&#xff1a;https://www.virtualbox.org/wiki/Downloads openEuler下载地址&#xff1a;https://www.openeuler.org/zh/download/?versionopenEuler%2022.03%20LTS%20SP3安装virtualbox virtualbox安装penEuler点击新建 …

1-04C语言执行过程

一、概述 本小节主要讲解一个C程序从源代码到最终执行的过程&#xff0c;这个过程又可以细分为两部分&#xff1a; 源代码到可执行文件的过程可执行文件在内存中执行 本小节是C语言基础当中&#xff0c;比较容易被初学者忽视的知识点。而实际上&#xff1a; 熟悉C程序从源文…

高光谱分类论文解读分享之基于生成对抗性少数过采样的高光谱图像分类

IEEE TGRS 2022&#xff1a;基于生成对抗性少数过采样的高光谱图像分类 题目 Generative Adversarial Minority Oversampling for Spectral–Spatial Hyperspectral Image Classification 作者 Swalpa Kumar Roy , Student Member, IEEE, Juan M. Haut , Senior Member, IE…

kubernetes RBAC Authentication 详解

开头语 写在前面&#xff1a;如有问题&#xff0c;以你为准&#xff0c; 目前24年应届生&#xff0c;各位大佬轻喷&#xff0c;部分资料与图片来自网络 内容较长&#xff0c;页面右上角目录方便跳转 Kubernetes 安全架构 K8S安全控制框架主要由下面3个阶段进行控制&#xf…

React 类组件和函数组件

组件component 一.概念 Element VS Component (元素与组件) //不成文的约定:元素小写&#xff0c;组件大写 const divReact.createElement(div,...) 这是一个React元素(小写) const Div()>React.createElement(div,...) 这是一个React组件(大写) 什么是组件? 能跟其他…

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

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

报错解决:RuntimeError: Error building extension ‘bias_act_plugin‘

系统&#xff1a; Ubuntu22.04&#xff0c; nvcc -V&#xff1a;11.8 &#xff0c; torch&#xff1a;2.0.0cu118 一&#xff1a;BUG内容 运行stylegan项目的train.py时遇到报错&#x1f447; Setting up PyTorch plugin "bias_act_plugin"... Failed! /home/m…

认知能力测验,⑦如何破解类比推理类测试题?

关于认知能力测评&#xff0c;今天这稿算是最后一篇&#xff0c;一共写了7篇&#xff0c;分别是数字推理、逻辑思维、语言常识、数量关系、图形推理、逻辑判断、和类比推理。 不论是校招、社招、网申、还是行测&#xff0c;在线人才测评已经是普遍普及的想象&#xff0c;而认知…

从源码角度来谈谈 HashMap

HashMap的知识点可以说在面试中经常被问到&#xff0c;是Java中比较常见的一种数据结构。所以这一篇就通过源码来深入理解下HashMap。 1 HashMap的底层是如何实现的&#xff1f;(基于JDK8) 1.1 HashMap的类结构和成员 /** HashMap继承AbstractMap,而AbstractMap又实现了Map的…

深入了解网络流量清洗--使用免费的雷池社区版进行防护

​ 随着网络攻击日益复杂&#xff0c;企业面临的网络安全挑战也在不断增加。在这个背景下&#xff0c;网络流量清洗成为了确保企业网络安全的关键技术。本文将探讨雷池社区版如何通过网络流量清洗技术&#xff0c;帮助企业有效应对网络威胁。 ![] 网络流量清洗的重要性&#x…

一个简单的MIPS-常见MIPS指令

ALU操作 这些指令用于执行算术和逻辑操作&#xff1a; ADDU&#xff08;无符号加法&#xff09;&#xff1a;将寄存器 rs 和 rt 的内容相加&#xff0c;结果存储在 rd 寄存器中。SUBU&#xff08;无符号减法&#xff09;&#xff1a;从寄存器 rs 减去寄存器 rt 的内容&#x…

RAG 最新最全资料整理

最近在做RAG方面的工作。它山之石可以攻玉&#xff0c;做了一些调研&#xff0c;包含了OpenAi&#xff0c;百川&#xff0c;iki.ai为我们提供的一些实现方案。 本文以时间顺序&#xff0c;整理了最近最新最全的和RAG相关的资料。都是满满的干货&#xff0c;包含了RAG评测工具、…