【问题标题】:Reducing memory footprint while using large XML DOM's in Java在 Java 中使用大型 XML DOM 时减少内存占用
【发布时间】:2011-12-12 17:10:38
【问题描述】:

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

首先,我们获取指向磁盘上客户端 XML 文件的原始 java.io.File 对象并将它们加载到集合中。然后我们遍历这个集合,为每个文件创建一个 apache.xmlbeans.XmlObject。在将所有文件解析为 XmlObjects 之后,我们创建 4 个集合来保存我们感兴趣的 XML 文档中的各个对象(要清楚,这些不是手工制作的对象,但我只能将其描述为创建的“代理”对象通过 apache 的 XMLBeans 框架)。作为最后一步,我们遍历这些集合以生成我们的 XML 文档(在内存中),然后将其保存到磁盘。

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

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

到目前为止我已经尝试过什么

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

我怀疑一个问题是我们为 123Mb 的 XML 文件以及从这些文档中获取的对象集合(使用 xpath 查询)持有一个 XmlObject[]。为了补救,我更改了逻辑,以便直接查询文档而不是查询这些集合。这里的想法是,在任何时候都不存在 4 个包含 10 个或 1000 个对象的大型列表,只是 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 的巨大绿色块占据。这让我相信这里的问题只是对象集合占用了大量空间,而不是占用未使用的内存。

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

【问题讨论】:

  • 也许大小无关紧要,但你会得到我的 +1 ;-)。

标签: java memory xml-parsing


【解决方案1】:

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

第一遍会将计算中所需的“交叉引用”数据解析为自定义对象并将它们存储在Maps。如果“交叉引用”数据很大,则考虑使用分布式缓存(如果您从 Maps 开始,Coherence 是最合适的选择)。

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

【讨论】:

  • 已标记为答案,虽然不可能在 2 次解析中进行此处理,但尝试这样做使我发现了大内存占用的真正原因。与相应的 XML 文档相比,Apache 的 XMLBeans 框架生成了一个不成比例的大 DOM,取消了对该框架的使用并编写了一个 SAX 解析器来填充我自己的 POJO 并解决了问题。
  • 是的,所有创建 XML 对象表示的 API 似乎都容易冗长,因此会占用内存。就个人而言,虽然最初会花费更多时间,但我更喜欢控制如何将 XML 加载到对象模型中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-12
  • 1970-01-01
  • 2018-11-24
  • 2014-04-07
  • 2022-08-23
  • 2016-12-12
相关资源
最近更新 更多