java.lang.UnsatisfiedLinkError: Native Library xxxx already loaded in another classloader after redeploying

Issue

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
at java.base/java.lang.ClassLoader$NativeLibrary.loadLibrary(ClassLoader.java:2456)
at java.base/java.lang.ClassLoader.loadLibrary0(ClassLoader.java:2684)
at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2649)

Restarting the application server solves the problem until the next development redeploy.

 

Environment

  • Liferay DXP 7.0-7.4

Resolution

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 UnsatisfiedLinkError to be thrown. For example, System.loadLibrary throws an UnsatisfiedLinkError when 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:

  1. Copy the problematic JAR into the folder:
    • DXP 7.0-7.3: [LIFERAY_HOME]/tomcat-9.x.x/webapps/ROOT/WEB-INF/lib
    • DXP 7.4: [LIFERAY_HOME]/tomcat-9.x.x/webapps/ROOT/WEB-INF/shielded-container-lib
  2. Go to the portal-impl.jar file and get from it the portal.properties file 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/7.4.3.24-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)
  3. Copy the  module.framework.system.packages.extra section to your portal-ext.propertiesfile
  4. Add at the end of the section separated by commas the library packages that you have copied to lib / shielded-container-lib

 
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.

 

Additional Information

 

 

¿Fue útil este artículo?
Usuarios a los que les pareció útil: 0 de 0