首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

辨析使用 ObjectOutputStream 可能引起的内存泄漏

2012-09-08 
剖析使用 ObjectOutputStream 可能引起的内存泄漏使用 ObjectOutputStream 来进行对象序列化相信大多数程

剖析使用 ObjectOutputStream 可能引起的内存泄漏

使用 ObjectOutputStream 来进行对象序列化

相信大多数程序员在使用 Java 进行日常开发工作中,都曾经遇到需要把数据进行序列化的情况,比如写入文件或者写入 socket 流。Java 的类库也提供了丰富工具类供我们使用,这其中就包括 ObjectOutputStream。此类允许我们将 Java 对象的基本数据类型和图形写入 OutputStream,在需要将 Java 对象进行序列化操作时,使用该类可以极大为我们提供便利。但是使用不当也会引起一些麻烦。

需要说明的是,在使用 ObjectOutputStream 向 OutputStream 流中写入的时候会在流中添加写入 Object 的信息。这样的话在我们使用 ObjectOutputStream 编写跨语言的 Socket 通信时会遇到问题。因为数据流中加入的是 Java 特有的信息,如 Class 类型以及成员变量的类型信息,只有使用 ObjectInputStream 才能解析这些特定的信息。

好了,通过上面的说明,我们现在对 ObjectOutputStream 有了一个大体的了解,接下来我们使用具体的代码来看一下如果使用 ObjectOutputStream 来把对象序列化到对象中。

回页首

把 Java 对象序列化到文件中

首先我们有一个简单的 Java 对象类:


清单 1. 要序列化的 java 对象


我们再来看一下写入的结果:


清单 7. 写入 5 次不同内容的对象

测试小结

通过上面的测试就很容易的发现,我们虽然写入了 5 次,但是不会每次写入都会插入写入对象和成员变量类型的信息,而是在第一次写入的时候插入一些头信息,以后再写就不会再插入了。这实际是 Java 做的优化,通过该优化从而减少 socket 传输的开销。回页首

避免长连接的情况下出现内存溢出

下面我们来谈谈如何避免该问题,说到这里我们就得提到 ObjectOutputStream 的 reset 方法了,JDK 文档中是这么解释该方法的:


“重置将丢弃已写入流中的所有对象的状态。重新设置状态,使其与新的 ObjectOutputStream 相同。将流中的当前点标记为 reset,相应的 ObjectInputStream 也将在这一点重置。以前写入流中的对象不再被视为正位于流中。它们会再次被写入流。”


就是说调用 reset 那么就丢弃所持有对象的状态(也就是释放掉了对对象的引用),同时会在流中设置 reset 标识。

我们来把之前的代码稍作修改,在进行一下测试来看看有什么不同:


清单 8. 重置的方式多次写入


我们来看一下加入 reset 后写入文件的内容:


清单 9. 重置的方式写入的内容

回页首

结论及一些建议

通过上面一系列的测试,我们大概对 Object 流有了一定了解,那么具体到我们日常编码中到底该不该调用 reset 呢,这个我想不能一概而论了。我们通过测试也看到了,在不调用 reset 的方式下,Java 的优化对于减轻 socket 开销还是很可观的,当然代价是有的,那就是直到你调用 reset 或者是关闭输出流之前,对于发送过的对象的实例是不会释放的。

如果你的程序需要很长时间的运行,建议你还是调用 reset 避免最后内存溢出程序崩溃,但是如果你又要长时间运行,且发送的消息量又很大,那么调用 reset 无疑会增加开销,那么这个时候最好的做法我觉得是自己实现一套机制,定时的调用 reset 或者是定量,比如查看到内存已经涨到一个水平后调用一下,这样既可以避免内存无限的增长下去,又可以减少不少 socket 通信的开销。


参考资料

学习

  • “Java 理论与实践: 用弱引用堵住内存泄漏”(developerWorks,2006 年 1 月):虽然用 Java 语言编写的程序在理论上是不会出现“内存泄漏”的,但是有时对象在不再作为程序的逻辑状态的一部分之后仍然不被垃圾收集。本文探讨了无意识的对象保留的常见原因,并展示了如何用弱引用堵住泄漏。

热点排行