はじめに
現在参画している案件でJNIの外部ライブラリを導入する際にClassLoader問題にハマったため、備忘録として解決方法を残したいと思います。
環境
Java8
wildfly-8.1.0.Final
JNIとは
Java Native Interface (JNI) は、Javaプラットフォームにおいて、Javaで記述されたプログラムと、他のプログラミング言語(たとえばCやC++など)で書かれた、実際のCPU上で動作するコード(ネイティブコード)とを連携するためのインタフェース仕様である。
(Wikipedia:https://ja.wikipedia.org/wiki/Java_Native_Interface より引用)
導入ライブラリについて
メイン処理がC++で実装された外部ライブラリで、JNIを利用してJavaに組み込もうとしていました。
ハマった点
ビルド資材を作成した際に以下のような構成にしていました。
service.ear
├ lib
│ └ jni.jar ← ここにJNIのjarを配置
├ META-INF
├ app1.jar
├ app2.jar
└ webapp.war
この資材をWildFlyにデプロイして起動すると1回目は正常に動作します。
問題は2回目以降にHotDeployすると以下のエラーが発生。
java.lang.UnsatisfiedLinkError: Native Library xxxx.dll already loaded in another classloader
「すでに別のクラスローダーにロードされています」だと...なぜや...
WildFlyを再起動するとこの事象は解消されますが、再度HotDeployすると再発します。
調査結果
ネイティブライブラリは1つのクラスローダーにしかロードできないという制限があるようです。
1回目も2回目も同じJVM上で実行されていますが、ロードするごとに異なるクラスローダーが使用されるため結果的に同じJVMで2回のロードが行われているためこの現象が発生するようです。
(と解釈しましたが、間違っていたらご指摘下さい)
対応その①
ビルド資材の中からJNIのjarファイルを除去しました。
service.ear
├ lib ← ここにJNIのjarを配置しない
├ META-INF
├ app1.jar
├ app2.jar
└ webapp.war
対応その②
WidlFlyに静的モジュールとしてjarを配置します。
wildfly-8.1.0.Final/modules/system/layers/base配下にフォルダを作成します。
(例)wildfly-8.1.0.Final/modules/system/layers/base/xxx/yyy/main
作ったフォルダにjarファイルとmodule.xmlを配置します。
base/xxx/yyy/main
├ jni.jar
└ module.xml ← 新規でファイルを作成
module.xmlの中身です。
maduleタグのnameに上で作成したディレクトリ構成と合わせます。
<?xml version="1.0" encoding="UTF-8"?>
<module name="xxx.yyy" xmlns="urn:jboss:module:1.1">
<resources>
<resource-root path="jni.jar"/>
</resources>
</module>
対応③
jboss-deployment-structure.xmlに依存関係を追加する。
<?xml version="1.0" encoding="UTF-8"?>
<jboss-deployment-structure>
<deployment>
<dependencies>
<module name="xxx.yyy" export="true" />
</dependencies>
</deployment>
</jboss-deployment-structure>
最後に
以上の対応でHotDeploy後もClassLoaderのエラーは出なくなりました。
他にミドルウェア周りに詳しいメンバーがいなかったため必死になって調べたので備忘として残しておこうと思いました。
本当にこの対応が正しいのかは不明ですが、ひとまず事象が解決したので良しとします。
参考
WildFlyのクラスローディング
https://qiita.com/tama1/items/f1556886149722b87f78