【问题标题】:java.lang.UnsatisfiedLinkError: Native Library XXX.so already loaded in another classloaderjava.lang.UnsatisfiedLinkError: Native Library XXX.so 已经加载到另一个类加载器中
【发布时间】:2016-08-24 12:47:23
【问题描述】:

我已经部署了一个 Web 应用程序,其中包含以下代码。

System.loadLibrary(org.opencv.core.Core.NATIVE_LIBRARY_NAME);

现在,我部署了另一个具有相同代码的 Web 应用程序。当它尝试加载库时,它会抛出以下错误。

Exception in thread "Thread-143" java.lang.UnsatisfiedLinkError: 
Native Library /usr/lib/jni/libopencv_java248.so already loaded in
another classloader

我想同时运行这两个应用程序。

到目前为止我已经尝试过:

  1. 在一个应用程序中加载库并在另一个应用程序中捕获上述异常
  2. 从两个应用程序中删除 jar 并将 opencv.jar 放入 Tomcat 的类路径(即在 /usr/share/tomcat7/lib 中)。

但以上都不起作用,有什么建议我可以做到这一点吗?

编辑:对于选项二,

System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

这条线有效,但当我实际要使用该库时出现异常。那是我关注的时候

Mat mat = Highgui.imread("/tmp/abc.png");

我得到了这个异常

java.lang.UnsatisfiedLinkError: org.opencv.highgui.Highgui.imread_1(Ljava/lang/String;)J
    at org.opencv.highgui.Highgui.imread_1(Native Method)
    at org.opencv.highgui.Highgui.imread(Highgui.java:362)

【问题讨论】:

  • 2 号应该有效。你确定你在其他地方删除了它,并且 webapp 中没有其他 jar 试图加载 lib?
  • @user2543253 请检查我编辑的问题。
  • 您能否检查Highgui 是否由与loadLibrary 相同的类加载器加载?否则原生方法不会被初始化。
  • Hii @user2543253 ,为了简单起见,我删除了我的第二个应用程序。现在我只部署了一个应用程序,它不包含 opencv jar。我把它放在 /usr/share/tomcat7/lib/ 目录下。我仍然收到“java.lang.UnsatisfiedLinkError: org.opencv.highgui.Highgui.imread_1(Ljava/lang/String;)J”异常。
  • 如何检查“Highgui 是否由与 loadLibrary 相同的类加载器加载?” @user2543253

标签: java opencv tomcat java-native-interface


【解决方案1】:

问题在于 OpenCV 如何处理原生库的初始化。

通常,使用本机库的类将具有加载该库的静态初始化程序。这样,类和本机库将始终在同一个类加载器中加载。使用 OpenCV,应用程序代码会加载本机库。

现在有一个本地库只能在一个类加载器中加载的限制。 Web 应用程序使用它们自己的类加载器,因此如果一个 Web 应用程序加载了本机库,另一个 Web 应用程序不能这样做。因此,加载原生库的代码不能放在 webapp 目录中,而必须放在容器(Tomcat)的共享目录中。当您有一个使用上述通常模式编写的类时(loadLibrary 在使用类的静态初始化程序中),将包含该类的 jar 放在共享目录中就足够了。但是,使用 OpenCV 和 Web 应用程序代码中的 loadLibrary 调用,本机库仍将加载到“错误”的类加载器中,您将获得 UnsatisfiedLinkError

要让“正确的”类加载器加载原生库,您可以创建一个小类,其中包含一个仅执行 loadLibrary 的静态方法。把这个类放在一个额外的 jar 中,并将这个 jar 放在共享的 Tomcat 目录中。然后在 Web 应用程序中,将对 System.loadLibrary 的调用替换为对新静态方法的调用。这样,OpenCV 类的类加载器和它们的原生库就会匹配,并且可以初始化原生方法。

编辑:评论者要求的示例

而不是

public class WebApplicationClass {
    static {
        System.loadLibrary(org.opencv.core.Core.NATIVE_LIBRARY_NAME);
    }
}

使用

public class ToolClassInSeparateJarInSharedDirectory {
    public static void loadNativeLibrary() {
        System.loadLibrary(org.opencv.core.Core.NATIVE_LIBRARY_NAME);
    }
}

public class WebApplicationClass {
    static {
        ToolClassInSeparateJarInSharedDirectory.loadNativeLibrary();
    }
}

【讨论】:

  • 如何在我的项目中使用 ToolClassInSeparateJarInSharedDirectory 类?我需要在我的项目中创建提供的依赖项或创建相同的类吗?
  • @rackom 我不确定我是否理解这个问题。上面的代码只是一个例子。如果你想使用它,你可以在你自己的项目中创建这样的代码。
  • 此设置不适用于 opencv4.0.1 任何关于那里可能有所不同的想法。我正在使用 Spring-boot 并尝试将 opencv4 加载到我的应用程序中。谢谢。
  • 不知道。最好使用您的代码中的 sn-ps 和确切的错误消息创建一个新问题。
  • 从 Tomcat 版本 9.0.13、8.5.35 和 7.0.92 开始,有一个内置解决方案。有关详细信息,请参阅下面的答案。
【解决方案2】:

从 javacpp>=1.3 开始,您还可以更改您的战争部署侦听器中的缓存文件夹(由系统属性定义):

System.setProperty("org.bytedeco.javacpp.cachedir",
                   Files.createTempDirectory( "javacppnew" ).toString());

请注意,虽然原生库总是被解包并且会被加载多次(因为被视为不同的库)。

【讨论】:

    【解决方案3】:

    从 Tomcat 版本 9.0.138.5.357.0.92 开始,我们添加了以下选项来解决此问题 BZ-62830

    1) 使用JniLifecycleListener 加载本机库。

    例如要加载opencv_java343 库,您可以使用:

    <Listener className="org.apache.catalina.core.JniLifecycleListener"
              libraryName="opencv_java343" />
    

    2) 使用org.apache.tomcat.jni.Library 中的load()loadLibrary() 而不是System

    例如

    org.apache.tomcat.jni.Library.loadLibrary("opencv_java343");
    

    使用这些选项中的任何一个都将使用 Common ClassLoader 来加载本机库,因此它将可供所有 Web 应用程序使用。

    【讨论】:

    • 我在这方面花了很多时间,不幸的是它对我不起作用。在 Tomcat (v8.5.38) 日志中,我看到了[info] Loaded native library xxx,但是当谈到从部署的 webapp 调用本机方法时,我得到了java.lang.UnsatisfiedLinkError: com.package.XxxxYyyy.methodZzz(Ljava/lang/String;)Ljava/lang/String;。我已经一起测试了选项 1 和 2,每个选项都单独进行了测试,但没有运气。我真的希望这会奏效,但它没有。
    • @marioosh 你确定本地库在正确的位置吗?它可以在任何类加载器上工作还是从任何一个都不工作?
    • @marioosh 你有没有解决这个问题?我遇到了同样的问题。无论我做什么,都无法避免不满意的链接错误 - 尝试将侦听器放在 context.xml、server.xml 中并以编程方式调用它,但没有任何效果。我看到它加载库很好,但不能调用它。非常令人沮丧,因为似乎没有其他人有这个问题,或者可能没有人经常尝试这样做?
    • 在服务器、主机和上下文级别使用 JniLifecycleListener 也不走运。共享我的本机库的最终解决方法是将 JNI API 上的包装器类打包到一个 jar 中,并编辑 catalina.properties 为:shared.loader="${catalina.base}/RUN/*.jar,放置包装器jar &assoc 依赖项(例如日志记录 jars)到 ${catalina.base}/RUN/ 位置。相同的依赖项从 App WAR 中删除(maven:provided)包装类具有静态 { System.loadLibrary("myNativeLib"); } 部分,以便在 catalina 共享类加载器中加载本机库。工作起来轻而易举。
    【解决方案4】:

    我被这个确切的问题困住了。

    在 Tomcat (v8.5.58) server.xml 文件中添加侦听器似乎在 Tomcat 启动时成功加载了 dll 文件(至少日志是这样说的),但是当您调用本机方法时,它会因 java.lang 而失败.UnsatisfiedLinkError.

    无论是否调用“org.apache.tomcat.jni.Library.loadLibrary("TeighaJavaCore");"在我的java代码中没有区别,同样的错误仍然存​​在。我在我的项目中包含 tomcat-jni 依赖项以启用“org.apache.tomcat.jni.Library.loadLibrary("TeighaJavaCore")”调用。虽然,我想没有必要在 Java 代码(在 Web 应用程序级别)中调用“org.apache.tomcat.jni.Library.loadLibrary("TeighaJavaCore")”,因为 TeighaJavaCore.dll 将在 Tomcat 启动时自动加载(因为上面的监听器是在 Tomcat 容器级别为此目的而定义的)

    我这里也查了一下“org.apache.tomcat.jni.Library.loadLibrary”的源代码,它只是调用了“System.loadLibrary(libname)”。

    https://github.com/apache/tomcat-native/blob/master/java/org/apache/tomcat/jni/Library.java

    【讨论】:

      猜你喜欢
      • 2010-11-16
      • 1970-01-01
      • 2018-02-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多