Jas*_*n C 11 java out-of-memory objectinputstream deserialization
当我从一个ObjectInputStreamwith中读取大量对象时,我遇到了一个OOM readUnshared.MAT指向其内部句柄表作为罪魁祸首,OOM堆栈跟踪(在本文末尾)也是如此.从各方面来看,这不应该发生.此外,OOM是否发生似乎取决于先前如何写入对象.
据有关该主题的写了,readUnshared要解决这个问题(而不是readObject通过读期间不创建句柄表条目)(即写了我是如何发现writeUnshared和readUnshared,这是我以前没有注意到).
但是,从我自己的观察看来,readObject和readUnshared相同的行为,以及OOM是否发生,如果对象被取决于写有reset()每个写操作后(它并没有,如果没关系writeObjectVS writeUnshared使用了,因为我以前认为的-我是我第一次参加考试时就累了.那是:
writeObject writeObject+reset writeUnshared writeUnshared+reset
readObject OOM OK OOM OK
readUnshared OOM OK OOM OK
因此,实际上是否readUnshared有任何影响似乎完全取决于对象的编写方式.这对我来说是令人惊讶和意想不到的.我确实花了一些时间来追踪代码路径但是,并且授予它已经很晚了我累了,我不清楚为什么它仍然会使用句柄空间以及为什么它将取决于对象的编写方式(但是,我现在有一个初步的怀疑,虽然我还没有确认,如下所述).readUnshared
从我所有的课题研究,到目前为止,它似乎writeObject与readUnshared 应工作.
这是我一直在测试的程序:
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.
}
}
Run Code Online (Sandbox Code Playgroud)
重新创建该程序问题的步骤:
testWrites取消注释(而testRead不是调用)运行测试程序,因此writeObject不会导致OOM.testRead取消注释(而testWrite不是调用),再次运行测试程序.要明确:我没有在同一个JVM实例中进行写入和读取.我的写作发生在与我的读取不同的程序中.由于我将写入和读取测试都塞入同一个源,因此上面的测试程序乍一看可能会有些误导.
不幸的是,我所处的真实情况是我有一个文件,其中包含许多用writeObject(没有reset)编写的对象,这将花费相当长的时间来重新生成(大约几天)(并且还reset使输出文件变得庞大) ,所以我想尽可能避免这种情况.另一方面,我目前无法读取该文件readObject,即使堆空间已达到我系统上可用的最大值.
值得注意的是,在我的实际情况中,我不需要对象流句柄表提供的缓存.
所以我的问题是:
readUnshared行为和对象的编写方式之间没有联系.这里发生了什么?writeObject和不写的reset?我不完全确定为什么readUnshared在这里没有解决问题.
我希望这很清楚.我在这里空着,所以可能会输入奇怪的单词.
来自以下答案的评论:
如果您没有调用
writeObject()JVM的当前实例,则不应该通过调用来消耗内存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)
Run Code Online (Sandbox Code Playgroud)这里是它发生的视频(视频最近的测试程序编辑记录之前,视频是相当于ReadMode.UNSHARED和WriteMode.NORMAL在新的测试程序).
下面是一些测试数据文件,其中包含30,000,000个对象(压缩大小为360 KB,但要警告它扩展到高达2.34 GB).这里有四个测试文件,每个都使用writeObject/ writeUnshared和的各种组合生成reset.在阅读行为只是它是如何写的依赖和独立的readObject对比readUnshared.请注意,writeObjectvs writeUnshared数据文件是逐字节相同的,我无法确定这是否令人惊讶.
我一直在盯着这里的ObjectInputStream代码.我目前怀疑的是这条线,出现在1.7和1.8中:
ObjectStreamClass desc = readClassDesc(false);
Run Code Online (Sandbox Code Playgroud)
该boolean参数true用于非共享和false正常的情况.在所有其他情况下,"非共享"标志会传播到其他调用,但在这种情况下,它是硬编码的false,因此在读取序列化对象的类描述时会导致句柄被添加到句柄表中,即使在readUnshared使用时也是如此.AFAICT,这是唯一不会传递给其他方法的非共享标志,因此我专注于它.
这是相反的,例如这条线,其中非共享标志是通过对传递readClassDesc.(readUnshared如果有人希望挖掘,您可以跟踪这两条线路的呼叫路径.)
但是,我还没有确认任何这些是重要的,或者有理由说为什么false在那里硬编码.这只是我正在研究的当前轨道,它可能被证明毫无意义.
此外,fwiw ObjectInputStream确实有一个私有方法clear,它清除句柄表.我做了一个实验,在每次阅读后我都会(通过反射)调用它,但它只是打破了一切,所以这是一个禁忌.
但是,如果对象是使用
writeObject()而不是编写的writeUnshared(),则readUnshared()似乎不会减少句柄表的使用。
那是对的。readUnshared()只会减少句柄表的使用量readObject()。如果您在使用writeObject()而不是位于同一个 JVM 中writeUnshared(),则句柄表使用率writeObject()不会减少readUnshared()。