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
Я хочу запустить эти оба приложения одновременно.
До сих пор, что я пытался:
- Загруженная библиотека в одном приложении и перехваченное исключение в другом приложении
- Удалил 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());
Обратите внимание, что нативные библиотеки всегда распаковываются и загружаются несколько раз (потому что рассматриваются как разные библиотеки).