Java 使用ObjectInputStream#readUnshared()时出现意外的OutOfMemoryError
当使用Java 使用ObjectInputStream#readUnshared()时出现意外的OutOfMemoryError,java,out-of-memory,deserialization,objectinputstream,Java,Out Of Memory,Deserialization,Objectinputstream,当使用readUnshared从ObjectInputStream读取大量对象时,我遇到了OOM。指向其内部句柄表作为罪魁祸首,OOM堆栈跟踪也是如此(在本文末尾)。据所有人说,这不应该发生。此外,OOM是否发生似乎取决于之前如何写入对象 根据,readUnshared应该通过在读取过程中不创建句柄表条目来解决这个问题(与readObject相反)(这就是我发现writeUnshared和readUnshared的原因,我以前没有注意到) 然而,从我自己的观察来看,readObject和read
readUnshared
从ObjectInputStream
读取大量对象时,我遇到了OOM。指向其内部句柄表作为罪魁祸首,OOM堆栈跟踪也是如此(在本文末尾)。据所有人说,这不应该发生。此外,OOM是否发生似乎取决于之前如何写入对象
根据,readUnshared
应该通过在读取过程中不创建句柄表条目来解决这个问题(与readObject
相反)(这就是我发现writeUnshared
和readUnshared
的原因,我以前没有注意到)
然而,从我自己的观察来看,readObject
和readUnshared
的行为相同,OOM是否发生取决于对象是否使用(如果使用了writeObject
vswriteUnshared
,这并不重要,正如我之前所想的那样——我只是在第一次运行测试时感到疲劳而已)。也就是说:
writeObject writeObject+reset writeUnshared writeUnshared+reset
readObject OOM OK OOM OK
readUnshared OOM OK OOM OK
使用该程序重新创建问题的步骤:
testWrite
s未注释(并且testRead
未调用),这样writeObject
不会导致OOMtestRead
未注释(和testWrite
未调用)writeObject
编写的(没有reset
),这将需要相当长的时间才能重新生成(以天为单位)(reset
也会使输出文件变得庞大),因此如果可能的话,我希望避免这种情况。另一方面,我当前无法使用readObject
读取文件,即使堆空间已达到系统上可用的最大值
值得注意的是,在我的实际情况中,我不需要对象流句柄表提供的缓存
因此,我的问题是:
readUnshared
的行为与对象的编写方式之间没有联系。这是怎么回事?writeObject
写入的,并且没有reset
,有什么方法可以避免读取时的OOM吗?readUnshared
无法解决这里的问题
我希望这是清楚的。我在这里运行的空白,所以可能输入了奇怪的单词
根据以下答案:
如果未在JVM的当前实例中调用
writeObject()
,则不应通过调用readUnshared()
来消耗内存
我所有的研究都显示了同样的结果,但令人困惑的是:
- 这是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)
- 这是一个(在最近的测试程序编辑之前录制的视频,视频相当于新测试程序中的
和ReadMode.UNSHARED
)WriteMode.NORMAL
- 以下是包含30000000个对象(压缩大小很小,只有360KB,但请注意,它会扩展到2.34GB)。这里有四个测试文件,每个文件都是通过
/writeObject
和writeUnshared
的各种组合生成的。读取行为仅取决于写入方式,与reset
与readObject
无关。请注意readUnshared
与writeObject
无关代码>数据文件是逐字节相同的,我无法决定这是否令人惊讶writeUnshared
我一直在盯着
ObjectInputStream
代码。我目前的嫌疑犯是,出现在1.7和1.8中:
ObjectStreamClass desc = readClassDesc(false);
其中,boolean
参数为true
表示非共享,为false
表示正常。在所有其他情况下,“非共享”标志被传播到其他调用,但在这种情况下,它被硬编码为false
,因此在读取序列化对象的类描述时,即使使用了readUnshared
,也会导致句柄被添加到句柄表中ds,这就是我关注它的原因
这与例如unshared标志传递到readClassDesc
的情况相反(如果有人想深入,您可以跟踪从readUnshared
到这两行的调用路径。)
然而,我还没有证实这其中的任何一个是重要的,也没有解释为什么false
是硬编码的。这只是我正在研究的当前轨迹,它可能被证明是没有意义的
另外,fwiw,ObjectInputStream
确实有一个私有方法,clear
,用于清除句柄表。我做了一个实验,在每次读取后调用它(通过反射),但它破坏了一切,所以这是不可能的
但是,如果对象是使用writeObject()
而不是writeUnshared()
编写的,那么readUnshared()
并不会减少句柄表的使用
这是正确的。readUnshared()
只会减少可归因于readObject()
的句柄表使用量。如果您所在的JVM使用的是writeObject()
而不是writeUnshared()
,则可归因于writeObject()
的句柄表使用量不会减少readU
ObjectStreamClass desc = readClassDesc(false);