在Java中使用大型XML DOM时减少内存占用

时间:2011-12-12 17:10:39

标签: java memory xml-parsing

我们的应用程序需要以XML格式(几个文件)呈现客户端数据,并将其解析为我们的通用XML格式(带有架构的单个文件)。为此,我们使用apache的XMLBeans数据绑定框架。下面简要介绍这个过程的步骤。

首先,我们将原始java.io.File对象指向磁盘上的客户端XML文件,并将它们加载到集合中。然后,我们遍历此集合,为每个文件创建一个apache.xmlbeans.XmlObject。在将所有文件解析为XmlObjects之后,我们创建了4个集合,其中包含我们感兴趣的XML文档中的各个对象(很明显,这些不是手工制作的对象,但我只能将其描述为' proxy& #39;由apache的XMLBeans框架创建的对象)。最后一步,我们迭代这些集合以生成我们的 XML文档(在内存中),然后将其保存到磁盘。

对于大多数用例,此过程运行正常,并且在给定' -Xmx1500m'时可以轻松地在JVM中运行。命令行参数。但是,当我们获得大型数据集时会出现问题。由客户。在这种情况下,大型的123Mb客户端XML分布在7个文件中。这样的数据集导致我们的代码内集合中填充了大约40,000个上述代理对象'。在这些情况下,内存使用只是通过屋顶。 我没有得到任何outofmemory异常程序只是挂起,直到垃圾收集发生,释放少量内存,然后程序继续,耗尽这个新空间并重复循环。这些解析会话目前需要4-5个小时。我们的目标是在一小时内将其降低。

需要注意的是,将客户端xml转换为xml所需的计算需要所有xml数据进行交叉引用。因此,我们无法实现顺序解析模型或将此流程批处理为较小的块。

到目前为止我尝试了什么

不是将所有123Mb的客户端xml保存在内存中,而是在每次请求数据时,加载文件,查找数据并释放对这些对象的引用。这似乎可以减少在此过程中消耗的内存量,但是您可以想象,常量I / O所花费的时间消除了减少内存占用的好处。

我怀疑一个问题是我们持有一个价值123Mb的XML文件的XmlObject []以及从这些文件中获取的对象集合(使用xpath查询)。为了解决这个问题,我改变了逻辑,以便不是查询这些集合,而是直接查询文档。这里的想法是,在任何时候都不存在4个大量的列表,其中包含10个对象中的10个,只是XmlObjects的大集合。这似乎没有任何区别,在某些情况下,甚至会增加内存占用。

现在抓住吸管,我认为在写入磁盘之前我们用来在内存中构建我们的 xml的XmlObject变得太大而无法与所有客户端数据一起维护。但是,对此对象执行一些sizeOf查询显示,在此最大的对象上,此对象小于10Kb。在阅读了XmlBeans如何管理大型DOM对象之后,它似乎使用了某种形式的缓冲编写器,因此很好地管理了这个对象。

所以现在我没有想法;不能使用SAX方法而不是内存密集型DOM方法,因为我们的应用程序中任何时候都需要100%的客户端数据,在我们绝对需要它之前不能阻止请求这些数据,因为转换过程需要大量的循环和磁盘I / O时间不值得保存的内存空间,我似乎无法构造我们的逻辑,以减少内部Java集合占用的空间量。我在这里运气不好吗?我必须接受,如果我想将123Mb的xml数据解析成我们的Xml格式,我无法用1500m的内存分配吗?虽然123Mb是我们域中的一个大型数据集,但我无法想象其他人从来没有必须同时使用Gb的数据做类似的事情。

其他可能很重要的信息

  • 我用过JProbe来试试看是否可以告诉我任何有用的东西。虽然我是一个剖析菜鸟,但我浏览了他们的内存泄漏和线程锁定教程,了解它们并且我们的代码中似乎没有任何泄漏或瓶颈。在使用大型数据集运行应用程序后,我们很快就会看到一个锯片“锯刀”。在内存分析屏幕上显示类型形状(参见附图),PS Eden空间被PS Old Gen的大块绿色区块接管。这让我相信这里的问题仅仅是对象集合占用的空间。而不是泄漏到未使用的记忆中。

JProbe trace of memory usage during parsing of large dataset

  • 我在64位Windows 7平台上运行,但这需要在32位环境下运行。

1 个答案:

答案 0 :(得分:3)

我采取的方法是对文件进行两次传递,在两种情况下都使用SAX。

第一遍将解析计算中所需的“交叉引用”数据到自定义对象中并存储它们Map。如果“交叉引用”数据很大,那么请查看使用分布式缓存(如果您从Map开始,Coherence是自然适合的。)

第二遍将解析文件,检索“交叉引用”数据以根据需要执行计算,然后使用javax.xml.stream API编写输出XML。