java.lang.UnsatisfiedLinkError: Native Library XXX.so already loaded in another classloader

java.lang.UnsatisfiedLinkError: Native Library XXX.so already loaded in another classloader

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

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

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

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 中)。

但是上面的none有效,有什么建议可以让我做到这一点吗?

编辑: 选项二,

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)

问题在于 OpenCV 如何处理本机库的初始化。

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

现在有一个本地库只能在一个 class 加载器中加载的限制。 Web 应用程序使用它们自己的 class 加载程序,因此如果一个 Web 应用程序加载了本机库,另一个 Web 应用程序将无法执行相同的操作。因此加载本地库的代码不能放在 webapp 目录中,而必须放在容器的 (Tomcat) 共享目录中。当你有一个用上面通常模式编写的 class(loadLibrary 在使用 class 的静态初始化程序中)时,将包含 class 的 jar 放在共享目录中就足够了。但是,使用 OpenCV 和 Web 应用程序代码中的 loadLibrary 调用,本机库仍将加载到 "wrong" class 加载程序中,您将获得 UnsatisfiedLinkError.

要让 "right" class 加载程序加载本机库,您可以使用仅执行 loadLibrary 的单个静态方法创建一个小型 class。将这个 class 放在一个额外的 jar 中,并将这个 jar 放在共享的 Tomcat 目录中。然后在 Web 应用程序中将对 System.loadLibrary 的调用替换为对新静态方法的调用。这样,OpenCV classes 的 class 加载器和它们的本机库将匹配,本机方法可以被初始化。

编辑:评论者要求的例子

而不是

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();
    }
}

自 javacpp>=1.3 起,您还可以在 war 部署侦听器中更改缓存文件夹(由系统 属性 定义):

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

请注意,尽管本机库始终会解压并将加载多次(因为被视为不同的库)。

从 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) 使用 load() or loadLibrary() from org.apache.tomcat.jni.Library 而不是 System.

例如

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

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

我卡在了这个确切的问题上。

在 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 将在 [=32] 时自动加载=] 开始(因为上面的侦听器是为此目的在 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