JAXB - 解组时出现 OutOfMemory: Java 堆空间错误

8

我目前正在尝试使用JAXB来解组一个XML文件,但是似乎这个XML文件太大了(约500MB),无法被解组器处理。我一直收到 java.lang.OutOfMemoryError: Java heap space 错误提示。

Unmarshaller um = JAXBContext.newInstance("com.sample.xml");
Export e = (Export)um.unmarhsal(new File("SAMPLE.XML"));

我猜测这是因为它试图将大的XML文件作为对象打开,但该文件对于Java堆空间来说过于庞大。

是否有其他更“内存高效”的解析大型XML文件的方法,即约500MB?或者可能有一个unmarshaller属性可以帮助我处理大型XML文件吗?

这是我的XML文件的样子:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- -->
<Export xmlns="wwww.foo.com" xmlns:xsi="www.foo1.com" xsi:schemaLocation="www.foo2.com/.xsd">
<!--- --->
<Origin ID="foooo" />
<!---- ---->
<WorkSets>
   <WorkSet>
      <Work>
         .....
      <Work>
         ....
      <Work>
      .....
   </WorkSet>
   <WorkSet>
      ....
   </WorkSet>
</WorkSets>

我希望能够在WorkSet级别进行反序列化,同时仍然能够阅读每个WorkSet中的所有工作。

4个回答

10

你的XML是什么样子的?对于大型文档,我建议使用StAX的XMLStreamReader,以便可以将文档分块解组为JAXB格式。

input.xml

在下面的文档中,有许多person元素的实例。我们可以使用带有StAX的JAXB XMLStreamReader逐个解组相应的Person对象,以避免内存耗尽。

<people>
   <person>
       <name>Jane Doe</name>
       <address>
           ...
       </address>
   </person>
   <person>
       <name>John Smith</name>
       <address>
           ...
       </address>
   </person>
   ....
</people>

演示

import java.io.*;
import javax.xml.stream.*;
import javax.xml.bind.*;

public class Demo {

    public static void main(String[] args) throws Exception  {
        XMLInputFactory xif = XMLInputFactory.newInstance();
        XMLStreamReader xsr = xif.createXMLStreamReader(new FileReader("input.xml"));
        xsr.nextTag(); // Advance to statements element

        JAXBContext jc = JAXBContext.newInstance(Person.class);
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        while(xsr.nextTag() == XMLStreamConstants.START_ELEMENT) {
            Person person = (Person) unmarshaller.unmarshal(xsr);
        }
    }

}

我们需要在要从中解组的XML片段的本地根元素上添加@XmlRootElement注释,而不是在整个XML文档的根元素上匹配。

@XmlRootElement
public class Person {
}

我在你最后一行代码中遇到了一个错误,并且需要(在你的示例中)强制转换 (Person) unmarshaller.unmarshal(xsr);。这样正确吗? - TyC
XMLStreamReader如何区分起始元素?例如,当它遇到任何起始元素时,它是否尝试创建Person的新实例? - TyC
1
@TyC - XMLStreamReader 只会以深度优先的顺序为我们提供访问 XML 事件的权限。关键在于我们需要识别出我们想要 JAXB 进行反编组的 XML 片段的起始元素状态。JAXB 将会将 XMLStreamReader 推进到该元素的末尾。然后,我们寻找下一个要进行反编组的片段。 - bdoughan
我的程序没有进入 while(xsr.nextTag() == XMLStreamConstants.START_ELEMENT)。一旦它到达这里,程序就会输出 null。我已经更新了我的 XML,是因为在到达 WorkSet 或者你的情况下的 Person 之前,它碰到了其他元素吗? - TyC
1
@Tyc - 你需要尝试推进 XMLStreamReader 来使事情变得正确。你可以询问 XMLStreamReader 当前节点的名称,以查看遍历的位置。 - bdoughan

5

您可以使用-Xmx启动参数来增加堆空间。

对于大文件,SAX处理更节省内存,因为它是事件驱动的,不会将整个结构加载到内存中。


2

我一直在进行大量的研究,特别是关于如何方便地解析非常大的输入集。虽然可以结合使用StaX和JaxB来选择性地解析XML片段,但这并不总是可能或可取的。如果您对此主题感兴趣,请查看以下内容:

http://xml2java.net/documents/XMLParserTechnologyForProcessingHugeXMLfiles.pdf

在本文档中,我描述了一种非常直接和方便的替代方法。它可以解析任意大的输入集,并以 javabeans 的方式让您访问数据。

1
使用SAXStAX。但如果目标是将文件表示为内存对象,您仍需要大量内存来保存如此大的文件内容。在这种情况下,您唯一的希望是使用-Xmx1024m JVM选项来增加堆大小(将最大堆大小设置为1024 MB)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接