ObjectInputStream引起的 Java 内存泄漏问题

网友投稿 886 2022-05-29

Java 的 ObjectOutputStream 和 ObjectInputStream 各自保留一个对已发送/已接收对象的引用的列表。就是这些引用,会阻止垃圾收集器对这些对象内存的释放。

当新对象的数量不断增长时(比方说在服务器中),最终将抛出"Java.lang.OutOfMemoryError"。解决办法就是使用 writeUnshared() 和 readUnshared() 方法来取代 writeObject() 和 readObject() 方法。

介绍

怎样在 Java 中创建一个内存泄漏?这是一个很好地面试题,因为 Java 编程中由于底层的自动垃圾收集功能而无需担心内存释放问题。但在复杂的情况下你仍然会遭遇 Java.lang.OutOfMemoryError 问题。这里就描述了一种这样的情况:由 java.io.ObjectInputStream 和 java.io.ObjectOutputStream 所实现的 Java 序列化机制所引起的内存泄漏。

原因

ObjectOutputStream 和 ObjectInputStream 都各自维护了一个对其已发送/已接收对象的引用表。所以当 ObjectOutputStream 重新发送某对象时,可以仍发送该对象的句柄,相应的 ObjectInputStream 则将接收到的句柄转换为先前接收到的对象的引用。Java 文档中这样描述:

使用共享机制 (…) 对单个对象的多个引用进行编码。

ObjectInputStream 所引起的 Java 内存泄漏问题

可以说,Java 的这一 feature 减少了带宽和内存的使用量,但也仅适用于通过链接定期重新发送对象的程序中。但在通过链接发送新对象的情况下,此功能除了不必要的(不断增长的)引用表以外没有任何作用。

垃圾收集器的工作原理就是清理不再被引用的对象。但由于 ObjectInputStream 始终保持了对其所接收的每个对象的引用,它阻止了垃圾收集器对通过流所接收的任何对象的清理。就在诸如服务器之类的程序中,它们接收到越来越多的对象,最终内存将被耗尽,引发 OutOfMemoryError。

情况识别和分析

OOM 可能有很多种不同的原因所造成。为了能够确定 OOM 是上文所讨论的 ObjectInputStream 相关 feature 所造成的,我们首先得来分析该进程的 heap dump。有很多种办法可以针对一个 Java 进程生成一个 heap dump,也有很多种办法来分析这些 dump 文件。我们可以通过将 JVM 命令行选项添加到进程执行命令行来生成 dump。还可以针对一个已经在运行中的程序来生成一个 dump。其中,通过添加命令行选项的方式可以在有 OOM 错误时生成 dump 文件(当然,还有一些其他命令行选项通过信号控制方式来在任意时间生成 dump,不管程序状态如何)。命令行选项如下:

-XX:+HeapDumpOnOutOfMemoryError

1

在 OOM 抛出时,一个名为 java_pid.hprof 的文件将会在该进程的执行目录所生成。有 n 多 dump 分析程序可以对 dump 文件进行分析,本文只对 jvm 原生自带的 VisualVM 进行介绍。

在 VisualVM 中,打开你收集到的 hprof 文件。在 classes 选项卡中,可以找到所有的类,这些类默认是按照 dump 中所存在的它们所拥有的实例数进行排序。当然,顶部的类极可能就是实例数暴多且导致内存错误的那个类。按字母顺序对类列表进行排序也是很有帮助的,这样可以将同一包的类分组在一起,进而可以对它们进行比较。

双击该列表中的一个类,视图切换至 instances 选项卡。对于每个实例,都有两个窗口。下边的那个保存指向该实例的参考路径。每个路径的边缘是一个垃圾收集 root(由三角形标记)。这样每个边缘点都是一个对象,该对象持有一个引用,防止 GC 对该对象的清理。如果很多这种爆炸的类实例都指向了一个 ObjectInputStream 对象,那么导致 OOM 的罪魁祸首可能就是本文所讨论的主题。

Java 任务调度

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:《智能系统与技术丛书 生成对抗网络入门指南》—2.3.2Keras使用入门
下一篇:无可信不安全,可信计算是数据安全的基石
相关文章