【问题标题】:java.lang.VerifyError: (class: GregorSamsa, method: ...) Illegal target of jump or branchjava.lang.VerifyError: (class: GregorSamsa, method: ...) 跳转或分支的非法目标
【发布时间】:2018-04-10 16:21:29
【问题描述】:

在创建大量具有相当复杂的 XSLT 文档(2000 年条件)的 xml 转换器 (javax.xml.transform.Transformer) 时,我遇到了一个 VerifyError。请看示例:

    public class XsltVerifyErrorTest {

    private static final int MAX_ITERATIONS_COUNT = 1000000;

    public static void main(String[] args) throws Exception {
        final byte[] xslBytes = Files.readAllBytes(new File(args[0]).toPath());

        for (int i = 0; i < MAX_ITERATIONS_COUNT; i++) {
            System.out.println(String.format("Iteration %d", i));

            final StreamSource xslSource = new StreamSource(new ByteArrayInputStream(xslBytes));
            final Transformer transformer = TransformerFactory.newInstance().newTransformer(xslSource);
        }
    }
}



m49216@ubuntu:~/xslt-verify-error-test$ javac XsltVerifyErrorTest.java
m49216@ubuntu:~/xslt-verify-error-test$ java -showversion XsltVerifyErrorTest XsltVerifyErrorTest.xsl
openjdk version "1.8.0_162"
OpenJDK Runtime Environment (build 1.8.0_162-8u162-b12-0ubuntu0.16.04.2-b12)
OpenJDK 64-Bit Server VM (build 25.162-b12, mixed mode)

Iteration 0
Iteration 1
Iteration 2
...
Iteration 79
Iteration 80
Iteration 81
Exception in thread "main" java.lang.VerifyError: (class: GregorSamsa, method: TestTemplate signature: (Lcom/sun/org/apache/xalan/internal/xsltc/DOM;Lcom/sun/org/apache/xml/internal/dtm/DTMAxisIterator;Lcom/sun/org/apache/xml/internal/serializer/SerializationHandler;ILjava/lang/Object;)V) Illegal target of jump or branch
        at java.lang.Class.getDeclaredConstructors0(Native Method)
        at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
        at java.lang.Class.getConstructor0(Class.java:3075)
        at java.lang.Class.newInstance(Class.java:412)
        at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getTransletInstance(TemplatesImpl.java:455)
        at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer(TemplatesImpl.java:486)
        at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl.newTransformer(TransformerFactoryImpl.java:762)
        at XsltVerifyErrorTest.main(XsltVerifyErrorTest.java:25)

带有测试数据的完整示例可以在here找到。

该问题可以在多个版本的 jdk 上重现:8u101、8u121、8u152、8u161、8u162。

有人遇到过这个问题吗?它看起来像一个 jvm 错误吗?

编辑 1: JDK 9 也受到影响,但重现问题要困难得多 - 在我的机器上大约需要 20 分钟和 660 次迭代。

java version "9.0.4"
Java(TM) SE Runtime Environment (build 9.0.4+11)
Java HotSpot(TM) 64-Bit Server VM (build 9.0.4+11, mixed mode)
Iteration 0
Iteration 1
Iteration 2
...
Iteration 658
Iteration 659
Iteration 660
Exception in thread "main" java.lang.VerifyError: (class: die/verwandlung/GregorSamsa, method: TestTemplate signature: (Lcom/sun/org/apache/xalan/internal/xsltc/DOM;Lcom/sun/org/apache/xml/internal/dtm/DTMAxisIterator;Lcom/sun/org/apache/xml/internal/serializer/SerializationHandler;ILjava/lang/Object;)V) Illegal target of jump or branch
    at java.base/java.lang.Class.getDeclaredConstructors0(Native Method)
    at java.base/java.lang.Class.privateGetDeclaredConstructors(Class.java:3110)
    at java.base/java.lang.Class.getConstructor0(Class.java:3315)
    at java.base/java.lang.Class.newInstance(Class.java:530)
    at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getTransletInstance(TemplatesImpl.java:552)
    at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer(TemplatesImpl.java:583)
    at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl.newTransformer(TransformerFactoryImpl.java:817)
    at XsltVerifyErrorTest.main(XsltVerifyErrorTest.java:19)

编辑 2: 在 JDK 10 上运行良好。

【问题讨论】:

  • 是的,这显然是 JDK 中的一个错误。该测试在 JDK 9 和 10 上运行良好。
  • @apangin 感谢您对此进行调查。实际上它也不适用于 JDK 9,但重现它要困难得多 - 我已经编辑了我的问题。

标签: java jvm verifyerror


【解决方案1】:

我有机会更深入地研究问题并最终解决了这个难题。
这确实是 JDK 的 BCEL 库中的一个 bug。

TransformerFactory 基于源 XSLT 生成字节码。字节码生成步骤之一是 remove temporary NOPs 并重新定位它们的目标器(指向它们的分支指令)。

目标在HashSet 中维护。这就是问题所在。 Instruction 类没有定义hashCode,它的equals 方法对分支完全错误:如果它们的目标相同,则认为两条分支指令相等。当然,这不是真的。

但只要分支 hashCode 不同,问题就不会发生。由于这些实际上是默认身份哈希码,因此它们的冲突机会非常小。但是经过多次迭代,由于生成的方法很大,最终出现了:两条不同的分支指令,目标相同,得到相同的标识hashCode,一条因冲突而覆盖另一条。

使用-XX:hashCode=2 运行程序(这将强制退化身份哈希码),它会立即崩溃。

该错误已在 BCEL-195 中修复,并在 JDK-8163121 中集成到 JDK 10。

早期的 JDK 版本仍然存在此错误,但幸运的是您可以使用以下解决方法。只需在应用程序启动时调用Instruction.setComparator

    import com.sun.org.apache.bcel.internal.generic.*;
    ...

    Instruction.setComparator((i1, i2) -> {
        if (i1 instanceof BranchInstruction) {
            return i1 == i2;
        }
        return InstructionComparator.DEFAULT.equals(i1, i2);
    });

【讨论】:

  • 脱帽致敬,令人印象深刻。解决方法对我有用!非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-12-31
  • 2016-05-11
  • 1970-01-01
  • 2014-10-22
  • 2014-09-19
  • 2021-03-26
相关资源
最近更新 更多