Java | XML 按大小拆分 | HashMap 性能问题 | OOM 堆 Space 错误
Java | XML Split by Size | HashMap Performance Issue | OOM Heap Space Error
需求是将XML个大于5MB的文档拆分成更小的文档块,以支持目标系统接受和处理it/them。因为 XSLT v2 似乎不支持 XML 按大小分割文档,所以我们最终编写了一个 java 程序。当文档很小或小于 10 MB 时,该程序运行良好。当输入一个 32 MB 的文件时,程序就会失败。该程序作为代理运行,并插入到最大内存设置为 25GB 的 JVM。尽管如此,我们始终看到 OOM 堆 space 错误。生成堆转储文件揭示了以下问题嫌疑人 1:
sun.misc.Launcher$AppClassLoader @ 0x1bb7ae098" occupies 156,512,240 (64.62%) bytes. The memory is accumulated in one instance of
基于此,我开始检查程序并推断出一个可能导致内存问题的地方,那就是[您可能会忽略一些系统输出,因为它们是为我的调试添加的 session]:
public static HashMap < Integer, String > splitPromotionItem(List promotionsItems, int promotionItemMaxSizeUoMNumericValue, int promotionItemMaxSize, String routingLocation, String docNum, XDNode messageHeader, XDNode promotionsData){
HashMap < Integer, String > promotionItemMap = new HashMap < Integer, String > ();
int totalSubMessage = 1;
String promotionsItemsData = "";
int promotionsItemsSize = 0;
String promotionsItemsDataTemp = "";
int i = 0;
int q = 1;
do {
promotionsItemsSize = promotionsItemsSize + ((XDNode) promotionsItems.get(i)).flatten().getBytes().length;
promotionsItemsData = promotionsItemsData + ((XDNode) promotionsItems.get(i)).flatten();
if (promotionsItemsSize > (promotionItemMaxSize * 1024 * 1024)) {
System.out.println("Inside First If: " + promotionsItems.size() + ": " + q++);
promotionsItemsSize = promotionsItemsSize - ((XDNode) promotionsItems.get(i)).flatten().getBytes().length;
promotionsItemsData = promotionsItemsDataTemp;
promotionItemMap.put(totalSubMessage++, promotionsItemsData);
if (i != (promotionsItems.size() - 1)) {
System.out.println("Inside Second If: " + promotionsItems.size());
i--;
promotionsItemsSize = 0;
promotionsItemsData = "";
} else {
System.out.println("Inside Second Else: " + promotionsItems.size());
promotionsItemsSize = ((XDNode) promotionsItems.get(i)).flatten().getBytes().length;
promotionsItemsData = ((XDNode) promotionsItems.get(i)).flatten();
}
}
if (promotionsItemsSize < (promotionItemMaxSize * 1024 * 1024) && (i) == (promotionsItems.size() - 1)) {
promotionItemMap.put(totalSubMessage++, promotionsItemsData);
}
i++;
promotionsItemsDataTemp = promotionsItemsData;
} while (i < promotionsItems.size());
return promotionItemMap;
}
该程序似乎首先将大型 XML 文档拆分为较小的块,这些块存储在 HashMap 中,然后将其提供给一个函数,该函数遍历映射中的每个条目并写入文件。文件名和其中一个元素带有文件在拆分批次中的索引和拆分总数,以便于识别。
我最初的想法是将代码修改为:与其将较小的 XML 块收集到 HashMap 中,不如将它们直接写入文件。这也要求在所有较小的块都保存到磁盘后,我必须重新打开它们以更新其内容以反映文件索引和总计数以及文件本身的名称。
有没有更好的方法来处理这个问题?请帮忙。
注意:JVM 每天处理大量数据并具有以下 start-up 选项,我们使用 saxon 作为 xslt 处理器:
-Djavax.xml.transform.TransformerFactory=net.sf.saxon.TransformerFactoryImpl -Xmx15360M -Xrs -XX:GCTimeRatio=5 -XX:+PrintGCDetails -Xloggc:<location> -XX:MinHeapFreeRatio=25 -XX:MaxHeapFreeRatio=60
更新29112017
classes XDNode 的使用及其函数 flatten 是使用 iWay 提供的 API 扩展程序的结果,以便能够 plug-in 代理进入它的 JVM 用于无缝执行流程。 XDNode 的官方定义如下:
XDNode 是 XML 树的单个元素。一个完整的文档是一个 XDNodes 树。 XDNode class 和树是为快速解析和搜索而设计的,并且在应用程序中易于操作。方法可用于在 XDNode 树和标准 JDOM 树之间进行转换。所有服务器操作都在 XDNodes 树上执行。
函数 flatten() returns 整个 XML 文档作为字符串。
下面是 XML 文档的示例:
拆分操作在元素/SalonApps/Promotion/PromotionData/PromotionItem处进行。我们遍历每次出现的 PromotionItem 并将迭代的块存储在一个临时变量中,如上面的代码所示。我们还在每次迭代开始时检查大小是否超过 5 MB [在 class 开始时定义] 的限制,以确定是否需要执行打包和 file-write 操作.当尺寸较小时,迭代进一步进行收集和存储。文档的 [header 部分 [/SalonApps/Promotion/MessageHeader] 被添加到每个拆分文档,修改了 MessageID 的值以反映批处理中拆分消息的索引以及第 2 个位置和第 2 个位置的批处理总数3rd 当值由连字符分隔时。
我们仅支持 XSLT v1 和 v2。如果 XSLT v1 或 v2 可用于按大小拆分 XML 文档,那就太好了。
我发现很难准确理解您要做什么,通过对您的示例代码进行逆向工程来获得任何见解当然也非常困难。但是您已经表达了对 XSLT 解决方案的兴趣,所以这里有一个建议。
如果您的文档基本上是以下形式的平面结构:
<table>
<record>...</record>
<record>...</record>
...
</table>
并且如果记录数是文档大小的合理代表,那么您可以使用
轻松地将其拆分成片段,每个片段的最大大小为 N(记录)
<xsl:template match="table">
<xsl:for-each-group select="record" group-adjacent="(position()-1) idiv $N">
<xsl:result-document href="part{position()}">
<table>
<xsl:copy-of select="current-group()"/>
</table>
</xsl:result-document>
</xsl:for-each-group>
</xsl:template>
另请注意,如果您使用 XSLT 3.0,此解决方案是可流式传输的(尽管在您开始处理 200Mb 或更多之前流式传输不是必需的)。
如果这不是您想要做的,那么您需要更清楚地解释您的要求。
你的问题的基本原因可能是这样的:
promotionsItemsData =
promotionsItemsData + ((XDNode) promotionsItems.get(i)).flatten();
您在其中通过增量字符串连接在循环中构建大字符串。这在 Java 是个坏消息;您应该使用 StringBuilder
.
构建字符串
这应该足以解决问题,尽管我个人会以完全不同的方式解决问题。我会根据应用于文档树视图的某些指标来决定在何处拆分文件,并选择将哪些节点放入每个输出部分,以常规方式序列化它们,而不是序列化节点和测量大小序列化部分。
需求是将XML个大于5MB的文档拆分成更小的文档块,以支持目标系统接受和处理it/them。因为 XSLT v2 似乎不支持 XML 按大小分割文档,所以我们最终编写了一个 java 程序。当文档很小或小于 10 MB 时,该程序运行良好。当输入一个 32 MB 的文件时,程序就会失败。该程序作为代理运行,并插入到最大内存设置为 25GB 的 JVM。尽管如此,我们始终看到 OOM 堆 space 错误。生成堆转储文件揭示了以下问题嫌疑人 1:
sun.misc.Launcher$AppClassLoader @ 0x1bb7ae098" occupies 156,512,240 (64.62%) bytes. The memory is accumulated in one instance of
基于此,我开始检查程序并推断出一个可能导致内存问题的地方,那就是[您可能会忽略一些系统输出,因为它们是为我的调试添加的 session]:
public static HashMap < Integer, String > splitPromotionItem(List promotionsItems, int promotionItemMaxSizeUoMNumericValue, int promotionItemMaxSize, String routingLocation, String docNum, XDNode messageHeader, XDNode promotionsData){
HashMap < Integer, String > promotionItemMap = new HashMap < Integer, String > ();
int totalSubMessage = 1;
String promotionsItemsData = "";
int promotionsItemsSize = 0;
String promotionsItemsDataTemp = "";
int i = 0;
int q = 1;
do {
promotionsItemsSize = promotionsItemsSize + ((XDNode) promotionsItems.get(i)).flatten().getBytes().length;
promotionsItemsData = promotionsItemsData + ((XDNode) promotionsItems.get(i)).flatten();
if (promotionsItemsSize > (promotionItemMaxSize * 1024 * 1024)) {
System.out.println("Inside First If: " + promotionsItems.size() + ": " + q++);
promotionsItemsSize = promotionsItemsSize - ((XDNode) promotionsItems.get(i)).flatten().getBytes().length;
promotionsItemsData = promotionsItemsDataTemp;
promotionItemMap.put(totalSubMessage++, promotionsItemsData);
if (i != (promotionsItems.size() - 1)) {
System.out.println("Inside Second If: " + promotionsItems.size());
i--;
promotionsItemsSize = 0;
promotionsItemsData = "";
} else {
System.out.println("Inside Second Else: " + promotionsItems.size());
promotionsItemsSize = ((XDNode) promotionsItems.get(i)).flatten().getBytes().length;
promotionsItemsData = ((XDNode) promotionsItems.get(i)).flatten();
}
}
if (promotionsItemsSize < (promotionItemMaxSize * 1024 * 1024) && (i) == (promotionsItems.size() - 1)) {
promotionItemMap.put(totalSubMessage++, promotionsItemsData);
}
i++;
promotionsItemsDataTemp = promotionsItemsData;
} while (i < promotionsItems.size());
return promotionItemMap;
}
该程序似乎首先将大型 XML 文档拆分为较小的块,这些块存储在 HashMap 中,然后将其提供给一个函数,该函数遍历映射中的每个条目并写入文件。文件名和其中一个元素带有文件在拆分批次中的索引和拆分总数,以便于识别。
我最初的想法是将代码修改为:与其将较小的 XML 块收集到 HashMap 中,不如将它们直接写入文件。这也要求在所有较小的块都保存到磁盘后,我必须重新打开它们以更新其内容以反映文件索引和总计数以及文件本身的名称。
有没有更好的方法来处理这个问题?请帮忙。
注意:JVM 每天处理大量数据并具有以下 start-up 选项,我们使用 saxon 作为 xslt 处理器:
-Djavax.xml.transform.TransformerFactory=net.sf.saxon.TransformerFactoryImpl -Xmx15360M -Xrs -XX:GCTimeRatio=5 -XX:+PrintGCDetails -Xloggc:<location> -XX:MinHeapFreeRatio=25 -XX:MaxHeapFreeRatio=60
更新29112017
classes XDNode 的使用及其函数 flatten 是使用 iWay 提供的 API 扩展程序的结果,以便能够 plug-in 代理进入它的 JVM 用于无缝执行流程。 XDNode 的官方定义如下:
XDNode 是 XML 树的单个元素。一个完整的文档是一个 XDNodes 树。 XDNode class 和树是为快速解析和搜索而设计的,并且在应用程序中易于操作。方法可用于在 XDNode 树和标准 JDOM 树之间进行转换。所有服务器操作都在 XDNodes 树上执行。
函数 flatten() returns 整个 XML 文档作为字符串。
下面是 XML 文档的示例:
拆分操作在元素/SalonApps/Promotion/PromotionData/PromotionItem处进行。我们遍历每次出现的 PromotionItem 并将迭代的块存储在一个临时变量中,如上面的代码所示。我们还在每次迭代开始时检查大小是否超过 5 MB [在 class 开始时定义] 的限制,以确定是否需要执行打包和 file-write 操作.当尺寸较小时,迭代进一步进行收集和存储。文档的 [header 部分 [/SalonApps/Promotion/MessageHeader] 被添加到每个拆分文档,修改了 MessageID 的值以反映批处理中拆分消息的索引以及第 2 个位置和第 2 个位置的批处理总数3rd 当值由连字符分隔时。
我们仅支持 XSLT v1 和 v2。如果 XSLT v1 或 v2 可用于按大小拆分 XML 文档,那就太好了。
我发现很难准确理解您要做什么,通过对您的示例代码进行逆向工程来获得任何见解当然也非常困难。但是您已经表达了对 XSLT 解决方案的兴趣,所以这里有一个建议。
如果您的文档基本上是以下形式的平面结构:
<table>
<record>...</record>
<record>...</record>
...
</table>
并且如果记录数是文档大小的合理代表,那么您可以使用
轻松地将其拆分成片段,每个片段的最大大小为 N(记录)<xsl:template match="table">
<xsl:for-each-group select="record" group-adjacent="(position()-1) idiv $N">
<xsl:result-document href="part{position()}">
<table>
<xsl:copy-of select="current-group()"/>
</table>
</xsl:result-document>
</xsl:for-each-group>
</xsl:template>
另请注意,如果您使用 XSLT 3.0,此解决方案是可流式传输的(尽管在您开始处理 200Mb 或更多之前流式传输不是必需的)。
如果这不是您想要做的,那么您需要更清楚地解释您的要求。
你的问题的基本原因可能是这样的:
promotionsItemsData =
promotionsItemsData + ((XDNode) promotionsItems.get(i)).flatten();
您在其中通过增量字符串连接在循环中构建大字符串。这在 Java 是个坏消息;您应该使用 StringBuilder
.
这应该足以解决问题,尽管我个人会以完全不同的方式解决问题。我会根据应用于文档树视图的某些指标来决定在何处拆分文件,并选择将哪些节点放入每个输出部分,以常规方式序列化它们,而不是序列化节点和测量大小序列化部分。