2017-03-11 20 views
11

ObjectInputStreamreadUnshared中读取大量对象时,我正在运行OOM。 MAT作为罪魁祸首指向内部处理表,OOM堆栈跟踪(在本文结尾处)也是如此。根据所有帐户,这不应该发生。此外,是否出现OOM似乎取决于以前如何写入对象使用ObjectInputStream时发生意外的OutOfMemoryError#readUnshared()

this write-up on the topicreadUnshared应该解决的问题通过读取过程中没有创建句柄表项(而不是readObject)(即写了是怎么发现的writeUnsharedreadUnshared,这是我以前没有注意到)。

但是,从我自己的观察看来,readObjectreadUnshared行为相同,与OOM是否发生,如果对象是书面reset() after each write(它不管writeObject VS writeUnshared是取决于正如我以前所想的那样 - 当我第一次运行测试时,我已经很累了)。那就是:

 
       writeObject writeObject+reset writeUnshared writeUnshared+reset 
readObject  OOM    OK    OOM     OK 
readUnshared  OOM    OK    OOM     OK 

所以无论是否readUnshared有任何影响实际上似乎是完全依赖于对象如何被。这对我来说是令人惊讶和意外的。我确实花了一些时间来追踪readUnshared code path,但是,并且认为它迟到了,而且我很累,它对我来说并不明显,为什么它仍然会使用句柄空间,以及为什么它要取决于对象的写法(但是,我现在有一个初步的嫌疑人,尽管我还没有确认,下面将会介绍)。

从我所有关于这个话题的研究到目前为止,看来writeObjectreadUnshared应该工作。

这是我一直在测试程序:

import java.io.BufferedInputStream; 
import java.io.BufferedOutputStream; 
import java.io.EOFException; 
import java.io.FileInputStream; 
import java.io.FileOutputStream; 
import java.io.IOException; 
import java.io.ObjectInputStream; 
import java.io.ObjectOutputStream; 
import java.io.Serializable; 


public class OOMTest { 

    // This is the object we'll be reading and writing. 
    static class TestObject implements Serializable { 
     private static final long serialVersionUID = 1L; 
    } 

    static enum WriteMode { 
     NORMAL,  // writeObject 
     RESET,  // writeObject + reset each time 
     UNSHARED, // writeUnshared 
     UNSHARED_RESET // writeUnshared + reset each time 
    } 

    // Write a bunch of objects. 
    static void testWrite (WriteMode mode, String filename, int count) throws IOException { 
     ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(filename))); 
     out.reset(); 
     for (int n = 0; n < count; ++ n) { 
      if (mode == WriteMode.NORMAL || mode == WriteMode.RESET) 
       out.writeObject(new TestObject()); 
      if (mode == WriteMode.UNSHARED || mode == WriteMode.UNSHARED_RESET) 
       out.writeUnshared(new TestObject()); 
      if (mode == WriteMode.RESET || mode == WriteMode.UNSHARED_RESET) 
       out.reset(); 
      if (n % 1000 == 0) 
       System.out.println(mode.toString() + ": " + n + " of " + count); 
     } 
     out.close(); 
    } 

    static enum ReadMode { 
     NORMAL,  // readObject 
     UNSHARED // readUnshared 
    } 

    // Read all the objects. 
    @SuppressWarnings("unused") 
    static void testRead (ReadMode mode, String filename) throws Exception { 
     ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(filename))); 
     int count = 0; 
     while (true) { 
      try { 
       TestObject o; 
       if (mode == ReadMode.NORMAL) 
        o = (TestObject)in.readObject(); 
       if (mode == ReadMode.UNSHARED) 
        o = (TestObject)in.readUnshared(); 
       // 
       if ((++ count) % 1000 == 0) 
        System.out.println(mode + " (read): " + count); 
      } catch (EOFException eof) { 
       break; 
      } 
     } 
     in.close(); 
    } 

    // Do the test. Comment/uncomment as appropriate. 
    public static void main (String[] args) throws Exception { 
     /* Note: For writes to succeed, VM heap size must be increased. 
     testWrite(WriteMode.NORMAL, "test-writeObject.dat", 30_000_000); 
     testWrite(WriteMode.RESET, "test-writeObject-with-reset.dat", 30_000_000); 
     testWrite(WriteMode.UNSHARED, "test-writeUnshared.dat", 30_000_000); 
     testWrite(WriteMode.UNSHARED_RESET, "test-writeUnshared-with-reset.dat", 30_000_000); 
     */ 
     /* Note: For read demonstration of OOM, use default heap size. */ 
     testRead(ReadMode.UNSHARED, "test-writeObject.dat"); // Edit this line for different tests. 
    } 

} 

步骤来重现问题与程序:

  1. 运行测试程序与testWrite小号注释掉(和testRead不叫)将堆大小设置为高,因此writeObject不会导致OOM。
  2. 第二次运行测试程序,其中testRead未注释(并且未调用testWrite),默认堆大小。

要清楚:我没有在同一个JVM实例中进行写入和读取操作。我的写作发生在我阅读的单独程序中。乍一看,上面的测试程序可能会有些误导,因为我将写入和读取测试都塞进同一个源。

不幸的是,真实的情况我在为我有一个包含了很多与writeObject(无reset)编写的对象,这将需要相当长的一段时间再生(天的量级)的文件(也reset使得输出文件变得庞大),所以如果可能的话,我想避免这种情况。另一方面,我目前还不能用readObject读取文件,即使堆栈空间达到我系统上的最大可用空间。

值得注意的是,在我的真实情况下,我不需要由对象流处理表提供的缓存。

所以我的问题是:

  1. 我所有的研究迄今表明readUnshared的行为,以及如何写的对象之间没有任何联系。这里发生了什么?
  2. 有没有什么办法可以避免读取OOM,因为数据是用writeObjectreset写的?

我不完全确定为什么readUnshared无法解决此问题。

我希望这很清楚。我在这里空着,所以可能会输入奇怪的单词。下面回答


comments

如果你不要求在JVM的当前情况下,你不应该通过调用readUnshared()被耗内存writeObject()

我所有的研究表明相同的,然而,令人混淆:

  • 这里是OOM堆栈跟踪,在readUnshared指出:

    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 
    at java.io.ObjectInputStream$HandleTable.grow(ObjectInputStream.java:3464) 
    at java.io.ObjectInputStream$HandleTable.assign(ObjectInputStream.java:3271) 
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1789) 
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350) 
    at java.io.ObjectInputStream.readUnshared(ObjectInputStream.java:460) 
    at OOMTest.testRead(OOMTest.java:40) 
    at OOMTest.main(OOMTest.java:54) 
    
  • 这里是一个video of it happening(视频在最近的测试程序编辑之前记录,在新测试程序中视频相当于ReadMode.UNSHAREDWriteMode.NORMAL)。

  • 这里是some test data files,其中包含30,000,000个对象(压缩的大小是一个小的360 KB,但被警告它扩展到高达2.34 GB)。这里有四个测试文件,每个测试文件用writeObject/writeUnsharedreset的各种组合生成。读取行为仅取决于写入方式,而与readObjectreadUnshared无关。请注意,writeObjectwriteUnshared数据文件是字节对字节相同的,我无法确定这是否令人惊讶。


我在ObjectInputStream代码from here一直盯着。我现在怀疑是this line,目前在1.7和1.8:

ObjectStreamClass desc = readClassDesc(false); 

如果这一boolean参数true非共享和false正常。在所有其他情况下,“非共享”标志会传播到其他调用,但在这种情况下,它会被硬编码为false,因此即使在使用readUnshared时,也会在读取序列化对象的类描述时将句柄添加到句柄表中。 AFAICT,这是只有发生的未共享标志没有被传递给其他方法,因此我为什么关注它。

这与例如this line其中非共享标志传递到readClassDesc。 (如果有人想挖掘,您可以追踪从readUnshared到这两条线路的呼叫路径。)

但是,我还没有确认这是否是重要的,或者说为什么false是硬编码的。这只是我正在研究的当前曲目,它可能证明毫无意义。

此外,fwiw,ObjectInputStream确实有一个私有方法,clear,清理句柄表。我做了一个实验,我在每次阅读后都会调用它(通过反射),但它只是打破了一切,所以这是一个不行。

回答

2

然而,似乎如果对象是使用writeObject()而非writeUnshared()写,然后readUnshared()不会降低句柄表使用。

这是正确的。 readUnshared()只会减少归因于readObject()的手柄表使用量。如果您使用writeObject()而不是writeUnshared()的同一JVM中,则归属于writeObject()的句柄表使用率不会减少readUnshared()

+0

嗯。当你说“在同一个JVM中”你是指同一个实例吗?在我的情况下,对象是由一个单独的程序编写的,我不在同一个运行中执行'writeObject'和'readUnshared'。在测试程序中,只有'testWrite'和'testRead'中的一个应该被取消注释并在任何给定时间运行。在运行读取之前,我的序列化数据文件已经存在。 –

+0

如果您在JVM的当前实例中未调用'writeObject()',则不应通过调用'readUnshared()'来占用内存。 – EJP

+0

这就是我要说的!它不应该发生。 :)然而,它的确如此。我已经为指向'readUnshared'和[视频证据](https://www.youtube.com/watch?v=7-ASZJEKsYI)的问题添加了堆栈跟踪。 –

相关问题