暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

IO流关闭到底关闭了什么

一十二章经 2021-02-01
2252

无论从网络Socket还是从本地文件系统读取二进制数据,程序都是以流的形式操作这些二进制数据。Java提供了两个超类InputStream、OutputStream提供了操作支持。我们在编码时用的最多的是这两个超类的派生类,如FileInputStream、DataInputStream等子类。当然,在操作完后我们还必须在程序中手动的调用.close( ) 方法关闭流,释放资源。那到底释放了什么资源?如果不调用close方法为什么会造成内存泄漏

将文件读进内存的步骤

我们以一个例子来说明

FileInputStream fileInputStream = new FileInputStream("/1.png");

Java程序中执行完上面一句代码后,Jvm就通过系统调用打开'1.png'这个文件(一张图片)。此时在操作系统内部维护了以下一段流程:

先从磁盘这个硬件开始讲起,机械磁盘最核心的三大件:柱面、磁头、扇区。只要确定了这三大件,那么文件数据就能够在磁盘上存取。为了更好的对磁盘进行操作,操作系统将这三大组件做了一层封装,这层封装叫做数据块(Block),这样我们读写文件只要操作Block就行,而Block与三大机械组件的映射关系由该文件的FCB(File Control Block)维护,即只要提供Block号,从FCB中就能找到三大组件的参数并操作磁盘。这就是FCB的作用,当打开一个文件时,会将该文件的FCB加载进v-node(virtual node)表中,v-node表也可以称为inode表即index node。

磁盘一个扇区的大小为512个byte,一个Block对应多个扇区,根据操作系统的不同,Block对应的扇区个数也不同,比如我手头上CentOS7系统中的Block就对应了8个扇区。运行下面命令查看sda1磁盘挂载到的文件系统中Block大小:

xfs_growfs /dev/sda1

有了Block后,操作系统为了上层应用更加舒服的操作文件又对Block进行了再次封装,这就是打开文件表。有了打开文件表后,我们就能根据文件字节流算出对应的Block。被程序open的文件相关的信息都会形成文件表存在于内核的打开文件表。有了打开文件表、FCB,应用程序就能很方便的对磁盘进行读写了。

这时在JVM中只需要有一个指针,指向打开的文件表,JVM就能操作文件。这个指针也就是文件描述符。文件描述符存在于JVM对应的PCB描述符表中。比如上图描述符表中fb1就是一个文件描述符,fb1指向了‘1.png’这张图片。

以上就是new FileInputStream("/1.png")
JVM操作1.png文件的过程。当然,读取文件都是先把数据读进内核态内存,然后拷贝到用户态内存,这里就不展开了,这两点内存空间的占用也就是该文件流内存泄漏的事发地。

通过测试案例深入理解

通过以上说明,站在JVM的角度,如果IO流不调用close方法,那么文件描述符表中永远会存在指向‘1.png’的fd1。这样就导致了一直有指针指向该文件的文件表,从而使得占用的内存无法得到释放,而且该文件还无法被删除,除非等到整个JVM停止

import java.io.FileInputStream;
public class Test01 {
    public static void main(String[] args) throws Exception {
        FileInputStream fileInputStream = new FileInputStream("/EthanHe/test/1.png");
//        fileInputStream.close();
        Thread.sleep(100000000);
    }
}

运行上面把close方法注释掉的程序,运行一下命令查看JVM的文件描述符表

[root@localhost dev]# jps
2178 Test01
2190 Jps
[root@localhost dev]# lsof -p 2178
COMMAND  PID USER   FD   TYPE DEVICE  SIZE/OFF     NODE NAME
java    2178 root  cwd    DIR  253,0       151 55524895 /EthanHe/test
java    2178 root    5r   REG  253,0     14543 55524908 /EthanHe/test/1.png

其中可以看到lsof输出的第二行中fd为5r的文件就是1.png

把close方法放开后再执行一遍

[root@localhost dev]# jps
2241 Jps
2229 Test01
[root@localhost dev]# lsof -p 2229
COMMAND  PID USER   FD   TYPE DEVICE  SIZE/OFF     NODE NAME
java    2229 root  cwd    DIR  253,0       151 55524895 /EthanHe/test
java    2229 root  rtd    DIR  253,0       268       64 /

可以发现1.png文件描述符从PCB中删除了

看到这里有人就可能怀疑是不是被GC回收掉了?如果怀疑的话那么我们可以显示的调用GCSystem.gc()
然后再运行上面的命令查看描述符,发现依然会存在‘1.png’这个文件的描述符。

总结

我们在编写程序的时候,对系统资源的调用一定要小心,因为JVM属于应用程序,它的权限相对来说还是比较低。如果对系统资源没有正确使用,那么可能会引发系统的崩溃等原因。好比本篇中对文件资源的调用,如果没有关闭系统资源,那么系统资源会被JVM这个程序一直占用,最后导致内存泄漏。


文章转载自一十二章经,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论