JAVA 关于JNI本地库加载
1.调用JNI的时候,通常我们使用System.loadLibrary(String libname)来load JNI library, 同样也可以使用System.load(String fileName)来load JNI library,两者的区别是一个只需要设置库的名字,比如如果libA.so 只要输入A就可以了,而libA.so的位置可以同过设置 java.library.path 或者 sun.boot.library.path,后者输入的是完整路经的文件名。而不论用什么方法,最后JNI 库是通过classloader 来加载的
例:
(1)System.load 参数为库文件的绝对路径,可以是任意路径。
Java代码
(2)System.loadLibrary 参数为库文件名,不包含库文件的扩展名
注意:这种方式,加载的dll文件须是在java.library.path这一jvm变量所指向的路径中。
可以通过如下方法来获得该变量的值:
Linux一般默认的java.library.path在/usr/lib
下。
也可以自己通过VM参数-Djava.library.path=/usr/lib
来显式的指定;或者通过增加环境变量export LD_LIBRARY_PATH=~/JavaNativeTest:$LD_LIBRARY_PATH
2.对于System.loadLibrary("NativeAgent");
在Linux下,动态库输出的文件名要是libNativeAgent.so
。
也就是说,如果System.loadLibrary("XXX")
;那么,在导出动态库时,动态库的名字就要是libXXX
。否则,会报错:
3.每个classloader 对象都有自己的nativeLibrary 数组,一个全局的systemNativeLibrary 数组,一个全局的已经加载过的loadLibraryNames数组,和一个正在加载过程中的记录栈nativeLibraryContext
对同一个classloader 对象可以重复加载相同的库,对不同的classloader只可以加载一次相同的库
(1). 这里定义的相同的库是指相同路经下的同一个文件
(2). 这里同样指出的是同一个classloader对象,而不是同一种classloader类型,比如说如果一种classloader类型初始化成2个classloader对象,那么这两个对象就不能重复加载相同的库。
(3). 重复加载,并不代表真的重复加载,而是代码中保护
4.报错处理:ava.lang.UnsatisfiedLinkError: Native Library kjdbc_jni already loaded in another classloader
原因:
1.java虚拟机不允许一个JNI本地库同时被两个不同的classloader加载。
2.web服务器自动重启机制
当tomcat重启web应用时会自动加载dll, 但是重启web应用并不是重启整个tomcat,上一次启动的jvm仍然存在(jvm依然认为dll已经被之前的classloader加载过了),就不允许重启后web应用的classloader再去加载它。
但是手动重启tomcat,会将上一次启动的jvm关闭并重新启动,这样就可以正常加载。
解决思路:
虽然不同的web应用使用不同的classloader,但是所有web应用classloader的父classloader是同一个(BootstrapClassLoader)
启动类加载器BootstrapClassLoader:是嵌在JVM内核中的加载器,该加载器是用C++语言写的,主要负载加载JAVA_HOME/lib下的类库,启动类加载器无法被应用程序直接使用。
根据双亲委托模型,只要让父classloader加载jni本地库就可避免被多个classloader加载
具体做法:
将加载dll的代码抽出来,单独放到放到一个类里,写到一个static代码块,(加载这个类时就会先运行static代码块),然后将这个类变成一个jar文件,放到tomcat\lib文件夹下。再在web.xml中加一个监听,在项目启动的时候就加载这个类,加载dll.