In our custom developments, we are using a Jar library that uses an external native loaded using JNI.
When we deploy our custom development in a clean environment everything works fine, but if we redeploy it, following error is thrown:
java.lang.UnsatisfiedLinkError: Native Library xxxx already loaded in another classloader
Restarting the application server solves the problem until the next development redeploy.
- Liferay DXP 7.0-7.4
The error is caused by the way the OSGI framework works when a module is stopped and restarted. In these cases, what OSGI does is create a new classloader where it reloads all the classes of the module, so at any given moment there may be a class loaded by different classloaders:
- the one corresponding to the one used by the module before stopping it
- and the one corresponding to the module after starting it again.
The old classloader is completely removed from memory once there are no longer any memory references to any classes loaded by it and the garbage collection is executed.
This can cause problems if the library is not OSGI-ready, for example, if it has static variables or uses native operating system libraries. In those cases, there may be references that are not released correctly.
On the other hand, in the specific case of native libraries, there is a restriction imposed by the JVM in which they cannot be loaded more than once by different classloaders, see https://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html#library_version
In the JDK, each class loader manages its own set of native libraries. The same JNI native library cannot be loaded into more than one class loader. Doing so causes
UnsatisfiedLinkErrorto be thrown. For example,
UnsatisfiedLinkErrorwhen used to load a native library into two class loaders
Therefore, for these cases of JARs that use native library loading through JNI, it would not be possible to load the JAR through OSGI without errors occurring if the module that includes the library is restarted individually. It would also not be possible for you to have multiple OSGI modules trying to load the same library.
Workaround using module.framework.system.packages.extra property
There is a workaround to load JAR libraries globally to the entire OSGI framework that consists of adding the JAR to the Liferay libraries folder and then configuring the OSGI framework to expose the packages of this JAR to the modules that make use of it.
This workaround should only be used if there is no other alternative and as long as the library does not collide with any package of any java class that Liferay comes with as standard.
In order to load the library globally, you would have to follow these steps, with the Liferay server stopped:
Copy the problematic JAR into the folder:
- DXP 7.0-7.3:
- DXP 7.4:
- DXP 7.0-7.3:
- Go to the portal-impl.jar file and get from it the
portal.propertiesfile from which you have to copy the module.framework.system.packages.extra section that will be similar to https://github.com/liferay/liferay-portal/blob/126.96.36.199-ga24/portal-impl/src/portal.properties#L7349-L7381 (note: this snippet may change with each new Liferay DXP product update that is released, so you will need to review it on each update)
- Copy the module.framework.system.packages.extra section to your
- Add at the end of the section separated by commas the library packages that you have copied to
This approach allows the classes of the library will be available for all the OSGI modules that need them.
For more information check the following article: Resolving ClassNotFoundException and NoClassDefFoundError in OSGi Bundles - The Missing Class Belongs to a Global Library
Finally, indicate that in the OSGI module that makes use of the JAR in question, you will have to declare the dependency with said package ("compileOnly" type dependencies only at compile time) if the module does not have said dependency declared, it will not have access to the classes in question.