我已经测量了使用 Jaxb2 将 XML 解组到对象的时间成本,使用具有较长(48 个字符)标记名称的大型 (1.7mb) XML 有效负载。我通过以采样模式运行的JProfiler观察到,字符串实习工作是所花费时间的很大一部分。
我做了一些研究,发现Jaxb可以在不实习字符串的模式下运行。我的理论是,在某些情况下,在解组期间不驻留字符串可以提高性能,但代价是使用更多的堆内存,因为在实习过程中不必对每个标签名称字符串进行哈希处理。
我用来抑制Jaxb的实习行为的方法是在我的Fastinfoset"StAXDocumentParser"(实现XMLStreamReader)上设置"org.codehaus.stax2.internNames"和"org.codehaus.stax2.internNsUris"属性。我不是 100% 清楚为什么您必须将它们设置为"true"以防止 Jaxb 实习字符串,但这就是它的工作原理。
这些 JUnit 驱动的测试是我用来得出结论的,禁用 Jaxb 的字符串实习行为会产生很大的性能差异:
https://github.com/gjd6640/fastinfoset-performance-evaluation
所以我的问题是多部分的:
1)我是否误解了一些重要的事情,不应该首先尝试禁用Jaxb的字符串实习行为?
2)有没有更好的方法来指导Jaxb不实习字符串?"StAXManager"类不允许你设置这些面向Woodstox的属性。对于这个测试,我最终扩展了StAXManager,如下所示以解决这个问题。这是我不想在生产中使用的黑客。我怀疑这里的想法是,当 Jaxb 从 Woodstox 流中解组时,它会查看 Woodstox 是否已经在进行实习,以及何时"是"Jaxb 通过禁用该过程的该步骤做出反应。我通过在 Jaxb 库中捎带该逻辑来作弊,所以想要一个更好的方法来解决这个问题。
package com.sun.xml.fastinfoset.stax;
public class JaxbStringInternSuppressionStaxManager extends StAXManager {
public JaxbStringInternSuppressionStaxManager() {
// Add to the allowable list of feature names so that the user may set these "StAXInputFactory" properties
super.features.put("org.codehaus.stax2.internNames", null);
super.features.put("org.codehaus.stax2.internNsUris", null);
}
}
更新:
像往常一样,"一个问题回答得好是半答"。我刚刚注意到在起草这个问题时,"com.sun.xml.internal.bind.v2.runtime.unmarshaller.StAXStreamConnector"检查类是否"com.sun.xml。internal.fastinfoset.stax.StAXDocumentParser"可从您正在使用的XMLStreamReader分配,如果是,则不启用字符串实习。就我而言,我的流对象是"com.sun.xml.fastinfoset.stax.StAXDocumentParser",因此实习不会被禁用。现在的问题是"为什么它只对 Fastinfoset 库的内部风格这样做?也许我会通过仔细阅读这篇文章找到答案。
另外,如果有更好的论坛来处理此类问题,例如活跃的开发人员用户组,请分享该信息,我将看到将它们链接到这篇文章,以便合适的人会看到这个问题。
如果不测量有和没有实习的真实用例,我不一定会相信分析器或测试,所以要有点怀疑。但是,实习生存在一些问题。特别是它使用固定大小的池大小,因此当池已满时,哈希查找的常量性能会降低到搜索链表。有关更长的讨论,请参阅 http://java-performance.info/string-intern-in-java-6-7-8/。
简而言之,您可以尝试使用-XX:StringTableSize=n
更改池大小(理想情况下 n 应该是素数),看看会发生什么。
使用-XX:+PrintStringTableStatistics
查看程序终止时池的使用情况,并尝试不同的大小。
编辑:这是试图回答"有没有更好的方法"(即让实习生更快)。我将把另一个问题留给更有资格的人。
解决方案选项 1:将整个应用程序交换到其他 jaxb 实现的简单方法
引入jaxb-impl以使用此Fastinfoset库性能更好的Jaxb版本:
<!-- Both of these libs must be here in order to get performant behavior out of Jaxb by default.
-->
<dependency>
<groupId>com.sun.xml.fastinfoset</groupId>
<artifactId>FastInfoset</artifactId>
<version>1.2.13</version>
<scope>compile</scope>
</dependency>
<dependency> <!-- This artifactId also exists under javax.xml.bind but it appears that nobody uses that one... -->
<groupId>javax.xml</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.1</version>
<scope>runtime</scope>
</dependency>
<!-- End: Both of these libs... -->
这将产生更新其余代码使用的 jaxb 版本的副作用。在某些情况下可能不可取。例如,如果您正在创建一个需要在各种应用程序中可用的共享库,那么当他们拉入您的共享组件时更改此功能是不礼貌的。
解决方案选项 2:使用 JVM 的 jaxb 实现和性能黑客来欺骗它相信字符串已经驻留(实现起来更复杂)
- 使用"maven-shade-plugin"来着色和重新打包Fastinfoset库的类。结果应该是一个无逻辑的 maven 组件。这是可选的,旨在确保使用 Fastinfoset 编解码器组件的用户不会因编解码器库拉入的传递依赖项而发生类路径冲突。
- 创建一个 my-fastinfoset-codec 库,该库提供一个简单的 API 来编码和解码 Fastinfoset 有效负载(考虑对参数使用 InputStreams 和 OutputStreams,对解码器的返回类型使用 XMLStreamReader)。添加对重新打包的 Fastinfoset 库的依赖关系。请注意,如果您使用 Eclipse,则在启用 m2e 的"工作区分辨率"时,它不能很好地处理着色库,因此请为您的编解码器项目禁用它。
- 向 my-fastinfoset-codec 添加一个类,该类扩展了重新打包的 Fastinfoset 库的"StAXManager"。此类应该有助于设置属性,这些属性告诉 jaxb 给定的 XMLStreamReader 已经暂留了 NS 和标记名称字符串。示例如下:
Package myrepackagedfastinfosetclassespackageprefix.shaded.com.sun.xml.fastinfoset.stax; import myrepackagedfastinfosetclassespackageprefix.shaded.com.sun.xml.fastinfoset.stax.StAXManager; public class JaxbStringInternSuppressionStaxManager extensions StAXManager { public JaxbStringInternSuppressionStaxManager() { 添加到允许的功能名称列表中,以便用户可以设置这些"StAXInputFactory"属性 super.features.put("org.codehaus.stax2.internNames", null); super.features.put("org.codehaus.stax2.internNsUris", null); }/** * 这是一个优化。默认情况下,FastInfoset 库已经允许字符串和 JVM 的 jaxb 实现 * 不必要地重复这项工作。至少对于 64 位版本的 jdk1.8.0_121,这是正确的。 * * 此解决方法的工作方式是搭载 Woodstox 解析器的 Jaxb 优化。当我们设置 * 这些属性它告诉 Jaxb,Woodstox 已经暂禁了字符串,这导致它禁用了 * 字符串实习。 * * 我们确实探索了更干净的选项,即引入 Maven "javax.xml:jaxb-impl" 工件作为依赖项,而不是使用 * JVM的jaxb库。当与 FastInfoset 库一起使用时,外部 jaxb 库的性能确实要好得多。 * 比 JVM 快,但不如禁用实习的 JVM 快 100%。我们停止探索该解决方案的关键原因 * 是当您重新打包(通过 maven-shade-plugin)JAXB 库时,它们不再适用于我们的标准 JAXB 绑定 * maven 组件,由于诸如"if ( instanceof my_repackaging_project.shaded.XMLElement)" * 在数据映射过程中使用。 */public JaxbStringInternSuppressionStaxManager enableTrickToStopJaxbFromInterningStrings() { super.setProperty("org.codehaus.stax2.internNames", true); super.setProperty("org.codehaus.stax2.internNsUris", true); 返回此; } }
解决方案选项 3:足够多与 Oracle 签订 JVM 支持合同的人提出请求某种非内部 fastinfoset 支持的人。
我希望 Oracle 教 JVM 提供的 jaxb 实现从给定的 XMLStreamReader 确定这个 Fastinfoset 实现被配置为实习字符串是相当简单的。
没有成功的解决方案可能性:重新包装上述解决方案1中的两个罐
子可以使用"maven-shade-plugin"或类似的东西来创建具有自定义前缀包名称的新jar。经过一番摆弄后,这确实适用于这些库。然而,我得出的最终结果是,重新打包的jaxb库现在希望jaxb-RI生成的OXM对象具有来自新着色包名称的注释。我的是按照标准方式构建的,因此重新打包的解决方案不会将任何数据映射到我的对象。我不愿意规定我们的 OXM 绑定库使用重新打包的 jaxb 库,我也不喜欢这种方法来探索更仔细地重新打包的方法,以免更改用于这些注释的包。
我没有探索的解决方案选项:
使用 JVM 的 fastinfoset 类,这些类的包名称中包含".internal."。这些可能会在JVM附带的jaxb实现中表现良好,但我拒绝将"未来的我"暴露在使用内部API带来的支持成本中。