【问题标题】:How to deal with LinkageErrors in Java?如何处理Java中的链接错误?
【发布时间】:2010-09-19 15:47:46
【问题描述】:

在开发一个大量基于 XML 的 Java 应用程序时,我最近在 Ubuntu Linux 上遇到了一个有趣的问题。

我的应用程序使用Java Plugin Framework,似乎无法将dom4j 创建的XML 文档转换为SVG 规范的Batik's 实现。

在控制台上,我得知发生了错误:

线程“AWT-EventQueue-0”java.lang.LinkageError 中的异常:接口 itable 初始化中的加载程序约束冲突:解析方法“org.apache.batik.dom.svg.SVGOMDocument.createAttribute(Ljava/lang/String;)Lorg 时/w3c/dom/属性;"当前类 org/apache/batik/dom/svg/SVGOMDocument 的类加载器(org/java/plugin/standard/StandardPluginClassLoader 的实例)和接口 org/w3c/ 的类加载器( 的实例) dom/Document 对于签名中使用的 org/w3c/dom/Attr 类型有不同的 Class 对象 在 org.apache.batik.dom.svg.SVGDOMImplementation.createDocument(SVGDOMImplementation.java:149) 在 org.dom4j.io.DOMWriter.createDomDocument(DOMWriter.java:361) 在 org.dom4j.io.DOMWriter.write(DOMWriter.java:138)

我认为这个问题是由 JVM 的原始类加载器和插件框架部署的类加载器之间的冲突引起的。

据我所知,无法为要使用的框架指定类加载器。破解它可能是可能的,但我更喜欢用不那么激进的方法来解决这个问题,因为(无论出于何种原因)它只发生在 Linux 系统上。

你们中有人遇到过这样的问题吗?知道如何解决它或至少找到问题的核心吗?

【问题讨论】:

    标签: java linux classloader linkageerror


    【解决方案1】:

    LinkageError 是您在一个经典案例中会遇到的情况,即您有一个由多个类加载器加载的类 C,并且这些类在同一代码中一起使用(比较、强制转换等)。如果它是相同的类名,或者即使它是从相同的 jar 加载的,都没有关系 - 如果从另一个类加载器加载,来自一个类加载器的类总是被视为不同的类。

    消息(这些年来已经改进很多)说:

    Exception in thread "AWT-EventQueue-0" java.lang.LinkageError: 
    loader constraint violation in interface itable initialization: 
    when resolving method "org.apache.batik.dom.svg.SVGOMDocument.createAttribute(Ljava/lang/String;)Lorg/w3c/dom/Attr;" 
    the class loader (instance of org/java/plugin/standard/StandardPluginClassLoader) 
    of the current class, org/apache/batik/dom/svg/SVGOMDocument, 
    and the class loader (instance of ) for interface org/w3c/dom/Document 
    have different Class objects for the type org/w3c/dom/Attr used in the signature
    

    所以,这里的问题在于解决 SVGOMDocument.createAttribute() 方法,该方法使用 org.w3c.dom.Attr(标准 DOM 库的一部分)。但是,使用 Batik 加载的 Attr 版本是从与您传递给方法的 Attr 实例不同的类加载器加载的。

    您会看到 Batik 的版本似乎是从 Java 插件加载的。而你的是从“”加载的,这很可能是内置的 JVM 加载器之一(引导类路径、ESOM 或类路径)。

    三个突出的类加载器模型是:

    • 委派(JDK 中的默认设置 - 先问父母,然后问我)
    • 委派后(常见于插件、servlet 和您想要隔离的地方 - 询问我,然后询问父母)
    • 兄弟(在 OSGi、Eclipse 等依赖模型中很常见)

    我不知道 JPF 类加载器使用什么委托策略,但关键是您希望加载一个版本的 dom 库,并且每个人都可以从同一位置获取该类。这可能意味着将其从类路径中删除并作为插件加载,或者阻止 Batik 加载它,或其他方式。

    【讨论】:

    • 还有哪些其他类加载器模型?
    【解决方案2】:

    听起来像是类加载器层次结构问题。我不知道您的应用程序部署在什么类型的环境中,但有时这个问题可能发生在 Web 环境中 - 应用程序服务器创建类加载器的层次结构,类似于:

    javahome/lib - 作为 root
    appserver/lib - 作为根的孩子
    webapp/WEB-INF/lib - 作为根的孩子的孩子
    等等

    通常,类加载器将加载委托给其父类加载器(这称为“parent-first”),如果该类加载器找不到该类,则子类加载器会尝试。例如,如果一个在 webapp/WEB-INF/lib 中部署为 JAR 的类尝试加载一个类,它首先请求 appserver/lib 对应的类加载器加载该类(进而请求 javahome/lib 对应的类加载器加载类),如果此查找失败,则搜索 WEB-INF/lib 以查找与此类的匹配项。

    在网络环境中,您可能会遇到这种层次结构的问题。例如,我之前遇到的一个错误/问题是 WEB-INF/lib 中的一个类依赖于部署在 appserver/lib 中的一个类,而后者又依赖于 WEB-INF/lib 中部署的一个类。这导致了失败,因为虽然类加载器能够委托给父类加载器,但它们不能委托回树。因此,WEB-INF/lib 类加载器会向 appserver/lib 类加载器请求一个类,appserver/lib 类加载器会加载该类并尝试加载依赖类,但由于在 appserver/lib 或 javahome 中找不到该类而失败/lib。

    因此,虽然您可能不会在 Web/应用服务器环境中部署您的应用,但如果您的环境设置了类加载器的层次结构,那么我过长的解释可能适用于您。可以? JPF 是否正在使用某种类加载器魔法来实现它的插件功能?

    【讨论】:

    • 关于你所描述的问题的例子,这是一个现实的场景吗?为什么appserver/lib 中的类会依赖于webapp/WEB-INF/lib 中的任何内容?
    • @JohnnyBaloney,这里的场景例如:appserver/lib 中的类可能使用一些动态链接来实现驻留在webapp/WEB-INF/lib 中的接口,例如从WebAppClass 调用ParentClass 中的一个例程,接下来的ParentClass 使用SomeServiceImpl(WEB-INF/lib) 类作为SomeService(来自appserver/lib 的接口)的实现。
    【解决方案3】:

    这可能会对某人有所帮助,因为它对我来说非常好。该问题可以通过集成您自己的依赖项来解决。遵循这个简单的步骤

    首先检查错误应该是这样的:

    • 方法执行失败:
    • java.lang.LinkageError:加载程序约束违规:
    • 当解析方法“org.slf4j.impl.StaticLoggerBinder.getLoggerFactory()Lorg/slf4j/ILoggerFactory;”
    • 当前类的类加载器(org/openmrs/module/ModuleClassLoader的实例),org/slf4j/LoggerFactory
    • 和解析类的类加载器(org/apache/catalina/loader/WebappClassLoader 的实例),org/slf4j/impl/StaticLoggerBinder
    • taticLoggerBinder.getLoggerFactory()Lorg/slf4j/ILoggerFactory 类型有不同的 Class 对象;用于签名

    1. 查看两个突出显示的类。谷歌搜索它们,如“StaticLoggerBinder.class jar 下载”和“LoggeraFactory.class jar 下载”。这将向您显示第一个或在某些情况下是第二个链接(站点为 http://www.java2s.com),这是您在项目中包含的 jar 版本之一。您可以自己巧妙地识别它,但我们沉迷于 google ;)

    2. 之后你会知道jar文件名,在我的例子中它就像slf4j-log4j12-1.5.6.jar & slf4j-api-1.5.8

    3. 现在此文件的最新版本可在此处http://mvnrepository.com/ 获得(实际上是迄今为止的所有版本,这是 maven 获取依赖项的站点)。
    4. 现在将两个文件添加为具有最新版本的依赖项(或保持两个文件版本相同,任一选择的版本都是旧的)。以下是您必须包含在 pom.xml 中的依赖项

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.7</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.7</version>
    </dependency>
    

    【讨论】:

      【解决方案4】:

      你能指定一个类加载器吗?如果没有,请尝试像这样指定上下文类加载器:

      Thread thread = Thread.currentThread();
      ClassLoader contextClassLoader = thread.getContextClassLoader();
      try {
          thread.setContextClassLoader(yourClassLoader);
          callDom4j();
      } finally {
          thread.setContextClassLoader(contextClassLoader);
      }
      

      我不熟悉 Java 插件框架,但我为 Eclipse 编写代码,并且不时遇到类似的问题。我不保证它会修复它,但它可能值得一试。

      【讨论】:

        【解决方案5】:

        Alex 和 Matt 的回答非常有帮助。我也可以从他们的分析中受益。

        在 Netbeans RCP 框架中使用 Batik 库时,我遇到了同样的问题,Batik 库作为“库包装模块”包含在内。如果其他一些模块使用 XML api,并且不需要为该模块建立对 Batik 的依赖关系,则会出现类加载器约束违规问题并出现类似的错误消息。

        在 Netbeans 中,各个模块使用专用的类加载器,模块之间的依赖关系意味着合适的类加载器委托路由。

        我可以通过简单地从 Batik 库包中省略 xml-apis jar 文件来解决该问题。

        【讨论】:

          【解决方案6】:

          this question 中所述,启用 -verbose:class 将使 JVM 日志信息与正在加载的所有类有关,这对于在更复杂的场景和应用程序中了解类的来源非常有帮助。

          您得到的输出大致如下(从那个问题复制而来):

          [Opened /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
          [Opened /usr/java/j2sdk1.4.1/jre/lib/sunrsasign.jar]
          [Opened /usr/java/j2sdk1.4.1/jre/lib/jsse.jar]
          [Opened /usr/java/j2sdk1.4.1/jre/lib/jce.jar]
          [Opened /usr/java/j2sdk1.4.1/jre/lib/charsets.jar]
          [Loaded java.lang.Object from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
          [Loaded java.io.Serializable from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
          [Loaded java.lang.Comparable from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
          [Loaded java.lang.CharSequence from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
          [Loaded java.lang.String from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
          

          【讨论】:

            【解决方案7】:

            我发现这个类被加载了两次。查找原因是parallelWebappClassLoader先自己加载类而不是使用它的父类加载器。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2015-06-09
              • 1970-01-01
              • 2021-12-16
              • 2014-04-05
              • 1970-01-01
              • 2011-10-27
              • 2017-08-21
              • 1970-01-01
              相关资源
              最近更新 更多