ObjectOutputStream - Object exceeding 1GB causes java.lang.OutOfMemoryError: Requested array size exceeds VM limit

ObjectOutputStream - Object exceeding 1GB causes java.lang.OutOfMemoryError: Requested array size exceeds VM limit

我正在尝试使用 Java 中的 ObjectOutputStream 将第三方可外部化 class 实例(Drools KnowledgePackage)的集合(ArrayList)写入文件 7. 如果我限制 KnowledgePackage 大小,那么生成的文件<= 1GB 一切都很好。如果我让实例进一步增长,这样(我相信)文件将大于 1GB,那么我会遇到以下故障。

代码如下所示:

Collection<KnowledgePackage> kpkgs = kbuilder.getKnowledgePackages();

ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(packageFileName) );
out.writeObject( kpkgs );
out.close();

错误如下所示:

Exception in thread "main" java.lang.OutOfMemoryError: Requested array size exceeds VM limit
        at java.util.Arrays.copyOf(Arrays.java:2271)
        at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:113)
        at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93)
        at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:140)
        at java.io.ObjectOutputStream$BlockDataOutputStream.drain(ObjectOutputStream.java:1876)
        at java.io.ObjectOutputStream$BlockDataOutputStream.setBlockDataMode(ObjectOutputStream.java:1785)
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1188)
        at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:347)
        at org.drools.rule.Package.writeExternal(Package.java:164)
        at org.drools.definitions.impl.KnowledgePackageImp.writeExternal(KnowledgePackageImp.java:161)
        at java.io.ObjectOutputStream.writeExternalData(ObjectOutputStream.java:1458)
        at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1429)
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1177)
        at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:347)
        at java.util.ArrayList.writeObject(ArrayList.java:742)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:988)
        at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1495)
        at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1431)
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1177)
        at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:347)
        ... <my code>

增加堆大小没有任何区别。说到这里好像有点别的意思了。

我认为原因是 ObjectOutputStream 的内部字节数组管理。根据 https://bugs.openjdk.java.net/browse/JDK-6991552 and https://bugs.openjdk.java.net/browse/JDK-6464834 ,每次现有数组用完时,数组大小都会增加 double + 1 。这意味着当数组达到 >=1GB 时,如果不破坏 Java.

的 (2^31)-1 最大数组大小,它就无法进一步增长

是否有一种变通方法或替代方法来编写这些对象,以便我至少可以输出高达 2GB,理想情况下是无限大小。也许存在另一种写入和读取此类大对象的方法?

已尝试 HotSpot 1.7.0_51 和 OpenJDK 1.7.0_45 结果相同。 如果相关,Drools 版本为 5.5.0Final

非常感谢

我通常对大型、复杂的对象使用替代序列化机制,例如 JSON 或 XML。但是,与其重写代码以使用 JSON 和 XML,您可以通过将大 class 的签名更改为 java.io.Exernalizable 并实现 readExternal() 来保留它和 writeExternal().

问题不在于 jdk,而是 Drools。如果查看堆栈跟踪,问题是对象正在序列化为 ByteArrayOutputStream。 jdk 没有这样做,这就是 org.drools.rule.Package.writeExternal 方法的实现方式: http://grepcode.com/file/repo1.maven.org/maven2/org.drools/drools-core/5.4.0.Final/org/drools/rule/Package.java#Package.writeExternal%28java.io.ObjectOutput%29

你应该用 drools 提交一个错误(关于序列化更大的规则包)。

或者,看起来好像您使用的是 DroolsObjectOutputStream,然后它会跳过辅助内存序列化并直接使用给定的流。这可能会解决您的问题(假设您可以将 DOOS 用于您的用例)。