19

I have deployed one web-application, which contains following code.

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

Now, I deployed another web-application which also have same code. When it tries to load library, it throwing following error.

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

I want to run these both application simultaneously.

Till now what I have tried:

  1. Loaded library in one application and caught above exception into another application
  2. Removed jars from both application and put opencv.jar into Tomcat's classpath(ie in /usr/share/tomcat7/lib).

But none of above worked, any suggestions by which I can do this ?

Edit: for option two,

System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

This line works but gets exception when I am actually going to use that library. That is when I do following

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

And I get this exception

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)
isapir
  • 21,295
  • 13
  • 115
  • 116
Bhushan
  • 1,489
  • 3
  • 27
  • 45
  • Number 2 should have worked. You sure you removed it elsewhere and that there isn't some other jar in the webapp that tries to load the lib? – user2543253 Apr 29 '16 at 12:17
  • @user2543253 Please check my edited question. – Bhushan Apr 29 '16 at 12:30
  • Can you check if `Highgui` is loaded by the same class loader that does the `loadLibrary`? Otherwise the native methods don't get initialized. – user2543253 Apr 29 '16 at 13:55
  • Hii @user2543253 , for the simplicity I removed my 2nd app. Now I have only one app deployed and it doesn't contain opencv jar. I put it in /usr/share/tomcat7/lib/ directory. Still I am getting "java.lang.UnsatisfiedLinkError: org.opencv.highgui.Highgui.imread_1(Ljava/lang/String;)J" exception. – Bhushan Apr 30 '16 at 06:44
  • How can I check "if Highgui is loaded by the same class loader that does the loadLibrary? " @user2543253 – Bhushan Apr 30 '16 at 09:38
  • I've written an explanation in an answer. – user2543253 May 13 '16 at 14:52

4 Answers4

19

The problem is with how OpenCV handles the initialization of the native library.

Usually a class that uses a native library will have a static initializer that loads the library. This way the class and the native library will always be loaded in the same class loader. With OpenCV the application code loads the native library.

Now there's the restriction that a native library can only be loaded in one class loader. Web applications use their own class loader so if one web application has loaded a native library, another web application cannot do the same. Therefore code loading native libraries cannot be put in a webapp directory but must be put in the container's (Tomcat) shared directory. When you have a class written with the usual pattern above (loadLibrary in static initializer of using class) it's enough to put the jar containing the class in the shared directory. With OpenCV and the loadLibrary call in the web application code however, the native library will still be loaded in the "wrong" class loader and you will get the UnsatisfiedLinkError.

To make the "right" class loader load the native library you could create a tiny class with a single static method doing only the loadLibrary. Put this class in an extra jar and put this jar in the shared Tomcat directory. Then in the web applications replace the call to System.loadLibrary with a call to your new static method. This way the class loaders for the OpenCV classes and their native library will match and the native methods can be initialized.

Edit: example as requested by a commenter

instead of

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

use

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

public class WebApplicationClass {
    static {
        ToolClassInSeparateJarInSharedDirectory.loadNativeLibrary();
    }
}
StevenWernerCS
  • 839
  • 9
  • 15
user2543253
  • 2,143
  • 19
  • 20
  • How can I use ToolClassInSeparateJarInSharedDirectory class in my project? Do I need to create provided dependency or create the same class in my project? – rackom May 07 '18 at 08:37
  • @rackom I'm not sure I understand the question. Above code is only an example. If you want to use it you create code like this in your own project. – user2543253 May 07 '18 at 11:41
  • This setup is not working with opencv4.0.1 any ideas on what can be different there. I am using Spring-boot and trying to load opencv4 into my app. Thanks. – delkant Feb 16 '19 at 17:35
  • Don't know. Best to create a new question with snippets from your code and the exact error message(s). – user2543253 Feb 18 '19 at 09:53
  • As of Tomcat versions 9.0.13, 8.5.35, and 7.0.92 there is a built-in solution. See my answer below for details. – isapir Mar 09 '19 at 18:26
  • Also all calls issued to the native code that belongs to the loaded native library must live in the same shared class loader where the native library is loaded – singidunumx Oct 19 '22 at 15:38
2

As of Tomcat versions 9.0.13, 8.5.35, and 7.0.92 we have added the following options to address this issue BZ-62830:

1) Use the JniLifecycleListener to load the native library.

e.g. to load the opencv_java343 library, you can use:

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

2) Use the load() or loadLibrary() from org.apache.tomcat.jni.Library instead of System.

e.g.

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

Using either of those options will use the Common ClassLoader to load the native library, and therefore it will be available to all of the Web Apps.

marioosh
  • 27,328
  • 49
  • 143
  • 192
isapir
  • 21,295
  • 13
  • 115
  • 116
  • 1
    I've spent a lot of time with that and unfortunately it does not work for me. In Tomcat (v8.5.38) logs i see `[info] Loaded native library xxx`, but when it comes to call native method from deployed webapp i got `java.lang.UnsatisfiedLinkError: com.package.XxxxYyyy.methodZzz(Ljava/lang/String;)Ljava/lang/String;`. I've been tested options 1 and 2 together and each one separately but without luck. I really had hope that will be working, but it did not. – marioosh Jun 28 '19 at 10:51
  • @marioosh Are you sure that the native library is in the right place? Does it work from any classloader or from none of them? – isapir Jun 28 '19 at 14:18
  • @marioosh did you ever figure this problem out? I'm experiencing the same problem. Can't avoid the unsatisfied link error no matter what i do - tried putting the listener in context.xml, server.xml and calling it programmatically but nothing works. I see it loading the library just fine, but can't call into it. Very frustrating as it seems like either no one else has this issue or maybe just no one tries to do this very often? – user3062913 Jun 30 '20 at 15:44
  • No luck either with JniLifecycleListener at server, host, and context levels. The final cure to share my native lib was to pack in a jar a wrapper class over the JNI API, and edit catalina.properties for: shared.loader="${catalina.base}/RUN/*.jar, placing the wrapper jar &assoc dependencies (e.g. logging jars) into the ${catalina.base}/RUN/ location. The same dependencies are removed from App WAR's (maven:provided) The wrapper class features a static { System.loadLibrary("myNativeLib"); } section in order to load the native lib in the catalina shared classloader. Works like a breeze. – Bernard Hauzeur Apr 27 '21 at 11:25
  • It's work. put your jar to ${catalina.base}/lib, then change your maven dependency of your jar scope to provided. Good luck. – Nguyen Duc Dung Mar 05 '23 at 18:19
0

I got stuck on this exact problem.

Adding listener in Tomcat (v8.5.58) server.xml file seems to successfully load the dll file (at least the log says so) when Tomcat starts, but when you call the the native method, it fails with java.lang.UnsatisfiedLinkError.

With or without calling "org.apache.tomcat.jni.Library.loadLibrary("TeighaJavaCore");" in my java code makes no difference, same error remain. I include tomcat-jni dependency in my project to enable the "org.apache.tomcat.jni.Library.loadLibrary("TeighaJavaCore")" call. While, I guess there is no need to call "org.apache.tomcat.jni.Library.loadLibrary("TeighaJavaCore")" in java code (at web application level) as the TeighaJavaCore.dll will be automatically loaded when Tomcat starts (because the listener above is defined for this purpose at Tomcat container level)

I also check the source code of "org.apache.tomcat.jni.Library.loadLibrary" here, it simply calls "System.loadLibrary(libname)".

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

-1

As of javacpp>=1.3 you may also change the cache folder (defined by system property) in your war deployment listener:

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

Note though that native libraries are always unpacked and will be loaded several times (because considered as different libs).

maison
  • 1
  • 1