java.lang.UnsatisfiedLinkError: Собственная библиотека XXX.so уже загружена в другой загрузчик классов

Я развернул одно веб-приложение, которое содержит следующий код.

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. Удалил jars из обоих приложений и поместил 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 ответа

Решение

Проблема в том, как OpenCV обрабатывает инициализацию нативной библиотеки.

Обычно класс, который использует собственную библиотеку, будет иметь статический инициализатор, который загружает библиотеку. Таким образом, класс и нативная библиотека всегда будут загружаться в один и тот же загрузчик классов. С OpenCV код приложения загружает собственную библиотеку.

Теперь есть ограничение, что нативная библиотека может быть загружена только в один загрузчик классов. Веб-приложения используют собственный загрузчик классов, поэтому, если одно веб-приложение загрузило собственную библиотеку, другое веб-приложение не может сделать то же самое. Поэтому загрузка кода собственных библиотек не может быть помещена в каталог веб-приложения, а должна быть помещена в общий каталог контейнера (Tomcat). Когда у вас есть класс, написанный с обычным шаблоном выше (loadLibrary в статическом инициализаторе использования класса) достаточно поместить банку, содержащую класс, в общий каталог. С OpenCV и loadLibrary вызовите код веб-приложения, однако нативная библиотека все равно будет загружена в "неправильный" загрузчик классов, и вы получите UnsatisfiedLinkError,

Чтобы заставить "правильный" загрузчик классов загрузить нативную библиотеку, вы можете создать крошечный класс с помощью одного статического метода, выполняющего только loadLibrary, Поместите этот класс в дополнительный jar и поместите этот jar в общий каталог Tomcat. Затем в веб-приложениях заменить вызов на 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();
    }
}

Начиная с версий Tomcat 9.0.13, 8.5.35, а также 7.0.92мы добавили следующие параметры для решения этой проблемы BZ-62830:

1) Используйте JniLifecycleListener чтобы загрузить родную библиотеку.

например, чтобы загрузить opencv_java343 библиотеку можно использовать:

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

2) Используйте load() или loadLibrary() от org.apache.tomcat.jni.Library вместо того System.

например

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

Использование любого из этих вариантов будет использовать Common ClassLoader для загрузки собственной библиотеки, и поэтому он будет доступен для всех веб-приложений.

Я застрял на этой проблеме.

Добавление слушателя в файл server.xml Tomcat (v8.5.58), похоже, успешно загружает файл dll (по крайней мере, в журнале так указано) при запуске Tomcat, но когда вы вызываете собственный метод, он не работает с java.lang.UnsatisfiedLinkError.

С вызовом или без вызова "org.apache.tomcat.jni.Library.loadLibrary("TeighaJavaCore");" в моем java-коде не имеет значения, остается та же ошибка. Я включаю в свой проект зависимость tomcat-jni, чтобы включить вызов org.apache.tomcat.jni.Library.loadLibrary("TeighaJavaCore") ". Хотя, я думаю, нет необходимости вызывать "org.apache.tomcat.jni.Library.loadLibrary(" TeighaJavaCore ")" в Java-коде (на уровне веб-приложения), поскольку 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

Начиная с javacpp>=1.3, вы также можете изменить папку кэша (определяется системным свойством) в слушателе развертывания войны:

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

Обратите внимание, что нативные библиотеки всегда распаковываются и загружаются несколько раз (потому что рассматриваются как разные библиотеки).

Другие вопросы по тегам