【问题标题】:ObjectInputStream from file causing memory leaks来自文件的 ObjectInputStream 导致内存泄漏
【发布时间】:2014-03-27 07:57:24
【问题描述】:

我有一个巨大的文件,其中包含由 ObjectOutputStream 一个接一个写入的对象列表。

for (Object obj : currentList){
    oos.writeUnshared(obj);
}

现在我想使用 ObjectInputStream 读取这个文件。但是,我需要同时读取多个文件,所以无法将整个文件读入内存。 但是,使用 ObjectInputStream 会导致 Heap Out Of Memory 错误。根据我的阅读,这是因为 ObjectInputStream 存在内存泄漏,并且即使在返回读取对象后仍保持对读取对象的引用。

我怎样才能要求 ObjectInputStream 不维护其阅读内容的引用?

【问题讨论】:

  • 我在写的时候关闭了流。在阅读它时,我同时阅读了大约 100 个文件,并且所有这些流都一起打开。我只有在读取了相应的文件后才关闭它们。但是,内存泄漏肯定会发生在 ObjectInputStream 中,因为在给定时间应该只有 100 个对象存储在内存中。
  • 为什么不尝试批量读取文件。例如,对于 100 个文件,您可以进行 4 个批次,这样最初将读取包含 25 个文件的批次 1,然后将读取批次 2 .. 等等..

标签: java memory-leaks objectinputstream objectoutputstream


【解决方案1】:

一种可能的解决方案是在您的 ObjectOutputStream 上调用方法 reset():“这将忽略已写入流的任何对象的状态。状态被重置为与新的 ObjectOutputStream 相同。流中的当前点被标记为重置,因此相应的 ObjectInputStream 将在同一点重置。” (摘自 java 文档) 对 ObjectOutputStream 进行重置也会重置 ObjectInputStream 状态。

我假设您也可以控制您的 ObjectOutputStreams?

【讨论】:

  • 是的,我可以控制 ObjectOutputStreams,但我之前使用 ObjectOutputStreams 写入文件,使用不同的程序而不是套接字连接。那么在读取时对 ObjectOutputStream 进行重置是否仍会影响 ObjectInputStream?
  • @copperhead 对象流既不知道也不关心它正在写入什么。我建议你试试看。
  • Waaau,它真的有效!我永远不会期望必须在输出端进行更改。我还注意到太频繁的reset()会显着扩展结果文件的大小。
【解决方案2】:

当您在写作方面使用writeUnshared 时,您已经完成了一半的工作。如果您现在还在输入端使用readUnshared 而不是readObject,ObjectInputStream 将不会维护对对象的引用。

您可以使用以下程序来验证该行为:

package lib.io;

import java.awt.Button;
import java.io.*;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.concurrent.ConcurrentHashMap;

public class ObjectInputStreamReferences {
  public static void main(String[] args)
  throws IOException, ClassNotFoundException {
    final int numObjects=1000;
    Serializable s=new Button();
    ByteArrayOutputStream os=new ByteArrayOutputStream();
    try( ObjectOutputStream oos=new ObjectOutputStream(os) ) {
      for(int i=0; i<numObjects; i++) oos.writeUnshared(s);
    }
    final ConcurrentHashMap<WeakReference<?>, Object> map
                                                  =new ConcurrentHashMap<>();
    final ReferenceQueue<Object> q=new ReferenceQueue<>();
    new Thread(new Runnable() {
      public void run() {
        reportCollections(map, q);
      }
    }).start();
    try(ObjectInputStream ois=
        new ObjectInputStream(new ByteArrayInputStream(os.toByteArray()))) {
      for(int i=0; i<numObjects; i++) {
        Object o=ois.readUnshared();
        map.put(new WeakReference<>(o,q), "");
        o=null;
        System.gc();Thread.yield();
      }
    }
    System.exit(0);
  }

  static void reportCollections(
      ConcurrentHashMap<WeakReference<?>, Object> map, ReferenceQueue<?> q) {
    for(;;) try {
      Reference<?> removed = q.remove();
      System.out.println("one object collected");
      map.remove(removed);
    } catch(InterruptedException ex){}
  }
}

【讨论】:

  • System.gc() 几乎什么都不做,而 Thread.yield() 甚至更少。
  • @EJP 感谢您的评论,但在我的机器上,获得一百个垃圾收集报告或根本没有报告是有区别的。我知道没有保证的行为,但至少在 Oracle 当前的 JVM 版本 7 中它可以工作,这就是它的全部意义,一段代码用于测试,而不是用于生产。
  • 你说的是这两种方法中的哪一种?
  • @EJP:我没有单独测试它们。我用那条线测试过,没有。但我想Thread.yield() 在今天的系统上确实已经过时了,但无论如何它并没有什么坏处。如前所述,我不会在生产代码中使用它们。
猜你喜欢
  • 2020-01-25
  • 2015-07-06
  • 2014-06-07
  • 2013-11-20
  • 2011-10-28
  • 2016-01-18
  • 2012-12-13
  • 1970-01-01
  • 2011-01-08
相关资源
最近更新 更多