热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

javagcleak_JavaIO09文件内存泄漏memoryleak

什么是内存泄露定义:当生命周期长的实例L不合理地持有一个生命周期短的实例S,导致S实例无法被正常回收代码例子publicclassAppSettings

什么是内存泄露

定义:当生命周期长的实例 L 不合理地持有一个生命周期短的实例 S,导致 S 实例无法被正常回收

代码例子

public class AppSettings {

private Context mAppContext;

private static AppSettings sInstance = new AppSettings();

//some other codes

public static AppSettings getInstance() {

return sInstance;

}

public final void setup(Context context) {

mAppContext = context;

}

}

解释

上面的代码可能会发生内存泄露

我们调用 1AppSettings.getInstance.setup() 传入一个Activity实例

当上述的 Activity 退出时,由于被 AppSettings 中属性 mAppContext 持有,进而导致内存泄露。

为什么上面的情况就会发生内存泄露

以 JAVA 为例,GC 回收对象采用GC Roots强引用可到达机制。

Activity实例被AppSettings.sInstance持有

AppSettings.sInstance由于是静态,被AppSettings类持有

AppSettings类被加载它的类加载器持有

而类加载器就是GC Roots的一种由于上述关系导致Activity实例无法被回收销毁。

ps: 内存泄漏,其实就是本来应该被回收的对象,因为被更长生命周期的对象持有,而无法回收的情况。

验证是否引起内存泄露

因此,想要证明未关闭的文件流是否导致内存泄露,需要查看文件流是否是GC Roots强引用可到达。

示例代码1(辅助验证GC 发生)

import java.io.BufferedReader;

import java.io.Reader;

class MyBufferedReader(`in`: Reader?) : BufferedReader(`in`) {

protected fun finalize() {

println("MyBufferedReader get collected")

}

}

示例代码2

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

findViewById(R.id.textview).setOnClickListener {

testInputStream()

}

}

private fun testInputStream() {

//需进入设置手动开启应用权限,未处理运行时权限问题

val `is` = FileInputStream("/sdcard/a.txt")

val buf = MyBufferedReader(InputStreamReader(`is`))

var line = buf.readLine()

val sb = StringBuilder()

while (line != null) {

sb.append(line).append("\n")

line = buf.readLine()

}

val fileAsString = sb.toString()

Log.i("MainActivity", "testInputStream.Contents : $fileAsString")

}

}

操作步骤

这里我们这样操作

点击textview视图,触发多次testInputStream

过几秒后,我们执行 heap dump。

我们使用 MAT 对上一步的dump文件进行分析(需进行格式转换)

分析结果

分析上图,我们发现

FileInputStream 只被 FinalizerReference 这个类(GC Root)持有

上述持有的原因是,FileInputStream重写了finalize,会被加入到FinalizerReference的析构处理集合

上述引用会随着Finalizer守护线程处理后解除,即FileInputStream实例彻底销毁。

所以,我们再来操作一波,验证上面的结论。

然后利用工具执行强制 GC 回收

过几秒后,我们执行heap dump。

我们使用 MAT 对上一步的dump文件进行分析(需进行格式转换)

堆分析文件,查找MyBufferedReader或者FileInputStream或者InputStreamReader 没有发现这些实例,说明已经GC回收

出于谨慎考虑,我们按照包名查找java.io在排除无关实例外,依旧无法找到testInputStream中的实例。再次证明已经被GC回收

因而我们可以确定,正常的使用流,不会导致内存泄露的产生。

当然,如果你刻意显式持有Stream实例,那就另当别论了。

为什么需要关闭流

看图

77f41736f762bf09a3d2e36c4aa864a7.png

如上图从左至右有三张表

file descriptor table 归属于单个进程

global file table(又称open file table) 归属于系统全局

inode table 归属于系统全局

从一次文件打开说起

当我们尝试打开文件 /path/myfile.txt

从 inode table 中查找到对应的文件节点

根据用户代码的一些参数(比如读写权限等)在 open file table 中创建 open file 节点

将上一步的 open file 节点信息保存,在 file descriptor table 中创建 file descriptor

返回上一步的 file descriptor 的索引位置,供应用读写等使用。

file descriptor 和流有什么关系

当我们这样 FileInputStream("/sdcard/a.txt") 会获取一个 file descriptor。

出于稳定系统性能和避免因为过多打开文件导致CPU和RAM占用居高的考虑,每个进程都会有可用的file descriptor 限制。

所以如果不释放file descriptor,会导致应用后续依赖file descriptor的行为(socket连接,读写文件等)无法进行,甚至是导致进程崩溃。

当我们调 用FileInputStream.close 后,会释放掉这个 file descriptor。

因此到这里我们可以说,不关闭流不是内存泄露问题,是资源泄露问题(file descriptor 属于资源)。

不手动关闭会怎样

不手动关闭的真的会发生上面的问题么?

其实也不完全是。

因为对于这些流的处理,源代码中通常会做一个兜底处理。

以 FileInputStream 为例

/**

* Ensures that the close method of this file input stream is

* called when there are no more references to it.

*

* @exception IOException if an I/O error occurs.

* @see java.io.FileInputStream#close()

*/

protected void finalize() throws IOException {

// Android-added: CloseGuard support.

if (guard != null) {

guard.warnIfOpen();

}

if ((fd != null) && (fd != FileDescriptor.in)) {

// Android-removed: Obsoleted comment about shared FileDescriptor handling.

close();

}

}

是的,在finalize方法中有调用close来释放file descriptor.

但是finalize方法执行速度不确定,不可靠

所以,我们不能依赖于这种形式,还是要手动调用close来释放file descriptor。

ps: 也就是说不会造成内存泄漏。实际导致的是资源泄漏,因为 finalize 回收的线程优先级非常低。

关闭流实践

手动关闭

private String readFirstLine() throws FileNotFoundException {

BufferedReader reader = new BufferedReader(new FileReader("test.file"));

try {

return reader.readLine();

} catch (IOException e) {

e.printStackTrace();

} finally {

try {

reader.close();

} catch (IOException e) {

e.printStackTrace();

}

}

return null;

}

TWR

Java 7 之后,可以使用 try-with-resource 方式处理

String readFirstLineFromFile(String path) throws IOException {

try (BufferedReader br = new BufferedReader(new FileReader(path))) {

return br.readLine();

}

}

个人收获

知识在于存疑

对于最常见的文件不关闭会内存泄漏,99% 的程序员都知道。

但是为什么会内存泄漏,估计知道的人少之又少。

jvm

jvm 的相关命令,一直停留于表面。

没有真正的去使用。

文件系统

操作系统的文件系统,如果没有系统学习,很多东西都是不知道的。

参考资料



推荐阅读
  • 流数据流和IO流的使用及应用
    本文介绍了流数据流和IO流的基本概念和用法,包括输入流、输出流、字节流、字符流、缓冲区等。同时还介绍了异常处理和常用的流类,如FileReader、FileWriter、FileInputStream、FileOutputStream、OutputStreamWriter、InputStreamReader、BufferedReader、BufferedWriter等。此外,还介绍了系统流和标准流的使用。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 本文讨论了在VMWARE5.1的虚拟服务器Windows Server 2008R2上安装oracle 10g客户端时出现的问题,并提供了解决方法。错误日志显示了异常访问违例,通过分析日志中的问题帧,找到了解决问题的线索。文章详细介绍了解决方法,帮助读者顺利安装oracle 10g客户端。 ... [详细]
  • 使用eclipse创建一个Java项目的步骤
    本文介绍了使用eclipse创建一个Java项目的步骤,包括启动eclipse、选择New Project命令、在对话框中输入项目名称等。同时还介绍了Java Settings对话框中的一些选项,以及如何修改Java程序的输出目录。 ... [详细]
  • OpenMap教程4 – 图层概述
    本文介绍了OpenMap教程4中关于地图图层的内容,包括将ShapeLayer添加到MapBean中的方法,OpenMap支持的图层类型以及使用BufferedLayer创建图像的MapBean。此外,还介绍了Layer背景标志的作用和OMGraphicHandlerLayer的基础层类。 ... [详细]
  • 使用freemaker生成Java代码的步骤及示例代码
    本文介绍了使用freemaker这个jar包生成Java代码的步骤,通过提前编辑好的模板,可以避免写重复代码。首先需要在springboot的pom.xml文件中加入freemaker的依赖包。然后编写模板,定义要生成的Java类的属性和方法。最后编写生成代码的类,通过加载模板文件和数据模型,生成Java代码文件。本文提供了示例代码,并展示了文件目录结构。 ... [详细]
  • 本文介绍了在PostgreSQL中批量导入数据时的优化方法。包括使用unlogged表、删除重建索引、删除重建外键、禁用触发器、使用COPY方法、批量插入等。同时还提到了一些参数优化的注意事项,如设置effective_cache_size、shared_buffer等,并强调了在导入大量数据后使用analyze命令重新收集统计信息的重要性。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
author-avatar
手机用户2502909693
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有