問題
私たちのカスタム開発では、 JNIを使用してロードされた外部ネイティブを使用する Jar ライブラリを使用しています。
カスタム開発したものをクリーンな環境にデプロイすると、すべて正常に動作しますが、再デプロイすると、以下のエラーが発生します:
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)
アプリケーションサーバーを再起動すると、次の開発の再デプロイまで問題が解決されます。
Environment
- Liferay DXP 7.0-7.4
解決策
このエラーは、モジュールの停止と再起動時のOSGIフレームワークの動作に起因しています。 このような場合、OSGIが行うのは、モジュールのすべてのクラスを再ロードする新しいクラスローダを作成することです:
- 停止する前にモジュールによって使用されたものに対応するもの。
- と、再度起動した後のモジュールに対応するものを選択します。
古いクラスローダーは、それによってロードされたクラスへのメモリ参照がなくなり、ガベージコレクションが実行されると、メモリから完全に取り除かれます。
これは、ライブラリがOSGIに対応していない場合、例えば、静的変数を持っていたり、ネイティブのオペレーティングシステムライブラリを使用している場合、問題を引き起こす可能性があります。 このような場合、正しく解放されない参照が存在する可能性があります。
一方、ネイティブ・ライブラリの場合、JVMが課す制限として、異なるクラスローダーによって複数回ロードすることはできません。 https://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html#library_versionを参照してください。
JDKでは、クラスローダーごとにネイティブライブラリのセットを管理しています。 同じJNIネイティブライブラリを複数のクラスローダーにロードすることはできません。 そうすると、
UnsatisfiedLinkErrorがスローされます。 例えば、System.loadLibraryは、UnsatisfiedLinkErrorをスローし、2つのクラスローダーにネイティブライブラリをロードするために使用します
したがって、このようなJNIによるネイティブライブラリのロードを利用するJARの場合、ライブラリを含むモジュールを個別に再起動しても、OSGIによるJARのロードにエラーが発生することはないでしょう。 また、複数のOSGIモジュールが同じライブラリをロードしようとすることもできないでしょう。
module.framework.system.packages.extra プロパティを使用した回避方法。
JARライブラリをOSGIフレームワーク全体にグローバルにロードする回避策があり、それはJARをLiferay librariesフォルダに追加し、このJARのパッケージをそれを利用するモジュールに公開するようにOSGIフレームワークを設定することからなります。
この回避策は、他に方法がない場合にのみ使用し、ライブラリがLiferayが標準装備しているJavaクラスのパッケージと衝突しない限りは使用する必要があります。
ライブラリをグローバルにロードするためには、Liferayサーバーを停止した状態で、以下の手順を踏む必要があります:
-
問題のあるJARをフォルダにコピーしてください:
- 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
- DXP 7.0-7.3です:
-
portal-impl.jar ファイルに行き、そこから
portal.propertiesfファイルを取得し、そこから module.framework.system.packages.extra section をコピーする必要があります。 https://github.com/liferay/liferay-portal/blob/7.4.3.24-ga24/portal-impl/src/portal.properties#L7349-L7381 (注意:このスニペットは新しい Liferay DXP 製品のアップデートごとに変更することがあるので、アップデートごとに確認する必要がある) -
module.framework.system.packages.extra セクションを
portal-ext.propertiesファイルにコピーします。 -
lib/shielded-container-libにコピーしたライブラリパッケージをカンマで区切ってセクションの最後に追加します。
このアプローチにより、ライブラリのクラスは、それを必要とするすべてのOSGIモジュールで利用可能になります。
詳しくは、以下の記事をご覧ください: Resolving ClassNotFoundException and NoClassDefFoundError in OSGi Bundles - The Missing Class Belongs to a Global Library
最後に、問題のJARを使用するOSGIモジュールで、当該パッケージとの依存関係を宣言する必要があることを示してください(コンパイル時にのみ依存する「コンパイルオンリー」タイプ)もしモジュールが当該依存関係を宣言しないなら、当該クラスへのアクセスはできません。
追加情報