ObjectOutputStream - 超过1GB的对象导致java.lang.OutOfMemoryError:请求的数组大小超过VM限制

时间:2016-02-12 15:13:45

标签: java out-of-memory drools objectoutputstream

我正在尝试使用Java 7中的ObjectOutputStream来编写第三方Externalizable类实例的集合(ArrayList)(Drools KnowledgePackage)。如果我限制了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的内部字节数组管理。根据{{​​3}}和https://bugs.openjdk.java.net/browse/JDK-6991552,每次现有数组耗尽时,数组大小将增加double + 1。这意味着当数组达到&gt; = 1GB时,它不会再进一步​​增长,而不会破坏Java的最大数组(2 ^ 31)-1。

是否有解决方法或替代方法来编写这些对象,因此我至少可以输出2GB,理想情况下是无限大小。也许存在一种编写和阅读这种大型物体的替代方法?

尝试过HotSpot 1.7.0_51和OpenJDK 1.7.0_45,效果相同。 如果它的相关,Drools版本是5.5.0Final

非常感谢

2 个答案:

答案 0 :(得分:4)

问题不在于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提交一个bug(关于序列化更大的规则包)。

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

答案 1 :(得分:0)

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