Java可以通过System.load 和 System.loadLibrary()加载动态库。
但是Java本身并没有提供unload的功能。
下面是在网上看到的一个load的原理及如何unload。地址:http://ppjava.com/?p=1273
Java加载JNI的动态库,有两种方式:
- public static void load(String filename),从作为动态库的本地文件系统中以指定的文件名加载代码文件。文件名参数必须是完整的路径名。调用 System.load(name) 实际上等效于调用:Runtime.getRuntime().load(name)。
- public static void loadLibrary(String libname),加载由 libname 参数指定的系统库。将库名映射到实际系统库的方法取决于系统。调用 System.loadLibrary(name) 实际上等效于调用:Runtime.getRuntime().loadLibrary(name)。
jni加载classpath中的动态链接库
2 |
System.loadLibrary( "test" ); |
具体方法请看这里→
jni加载jar包中的动态链接库
部署应用的时候要加载jar包中的动态链接库文件,可以将本地库拷贝到环境变量path指定的路径中。一般在windows平台上直接copy到“C:\WINDOWS\System32”目录下了事,但这样需要用户做额外操作,有时候当前系统用户也没有权限拷贝库文件到指定目录。
有人可能会想到,在Java代码中利用System.setProPerty设置lib path,指向动态库文件所在路径。不过此法不可行,因为一旦Java虚拟机启动以后,lib path就是只读的,就不能再设置进去值了。
这个问题可以这样解决:
把dll放在classpath中,用Class.getResource(str).openStream()读取这个dll,
- 拷贝到classpath中,用System.loadLibrary(name)加载;
- 如果没有权限拷贝到指定目录,也可以拷贝到temp目录中,用System.load(path)加载。
1. 拷贝到classpath中,用System.loadLibrary(name)加载
3 |
FileOutputStream out = null ; |
5 |
String libpath = System.getProperty( "java.library.path" ); |
6 |
if (libpath == null || libpath.length() == 0 ) |
7 |
throw new RuntimeException( "java.library.path is null" ); |
9 |
String pathSeparator = System.getProperty( "path.separator" ); |
10 |
StringTokenizer st = new StringTokenizer(libpath, pathSeparator); |
11 |
if (st.hasMoreElements()) |
12 |
path = st.nextToken(); |
14 |
throw new RuntimeException( "can not split library path : " + libpath); |
15 |
in = Foo. class .getResource( "foo.dll" ).openStream(); |
16 |
File fooDll = new File( new File(path), "foo.dll" ); |
17 |
out = new FileOutputStream(fooDll); |
18 |
byte [] buffer = new byte [ 2048 ]; |
20 |
while ((len = in.read(buffer)) != - 1 ) |
21 |
out.write(buffer, 0 , len); |
23 |
fooDll.deleteOnExit(); |
24 |
System.loadLibrary( "foo" ); |
25 |
} catch (Throwable e) { |
2. 拷贝到temp目录中,用System.load(path)加载
3 |
FileOutputStream out = null ; |
5 |
in = Foo. class .getResource( "/foo.dll" ).openStream(); |
6 |
File temporaryDll = File.createTempFile( "foo" , ".dll" ); |
7 |
out = new FileOutputStream(temporaryDll); |
8 |
byte [] buffer = new byte [ 2048 ]; |
10 |
while ((len = in.read(buffer)) != - 1 ) |
11 |
out.write(buffer, 0 , len); |
13 |
temporaryDll.deleteOnExit(); |
14 |
System.load(temporaryDll.getPath()); |
15 |
} catch (Throwable e) { |
为什么上面通过getResource取得了URL不直接去加载呢?因为如果把dll和class一起打成jar包,ClassLoader还是不能加载本地库,因为System.load(path)需要的是dll的完整路径,但并不支持jar协议。ClassLoader中用new File(name),当然会找不到文件。
1 |
URL url = Foo. class .getResource( "/java/lang/String.class" ); |
2 |
System.out.println(url); |
3 |
System.out.println(url.toExternalForm()); |
4 |
System.out.println(url.getFile()); |
以上代码输出结果如下:
jar:file:/E:/Java/jdk1.6.0_31/jre/lib/rt.jar!/java/lang/String.class
jar:file:/E:/Java/jdk1.6.0_31/jre/lib/rt.jar!/java/lang/String.class
file:/E:/Java/jdk1.6.0_31/jre/lib/rt.jar!/java/lang/String.class
jni卸载动态库文件
事实证明以上调用deleteOnExit()方法并不能在系统退出后删除动态库文件,由于程序占用而导致无法删除,所以要在程序退出时卸载动态库文件,这个样在程序退出时就可以删除动态临时创建的动态库文件了。我们在程序中加个hook,让程序退出时卸载动态链接库:
1 |
Runtime.getRuntime().addShutdownHook( new Thread() { |
4 |
unloadNativeLibs(temporaryDll.getName()); |
那么如何在程序推出时卸载动态库文件呢?可以通过反射调用私有属性和私有方法来卸载:
1 |
public static synchronized void unloadAllNativeLibs() { |
3 |
ClassLoader classLoader = Foo. class .getClassLoader(); |
4 |
Field field = ClassLoader. class .getDeclaredField( "nativeLibraries" ); |
5 |
field.setAccessible( true ); |
6 |
Vector<Object> libs = (Vector<Object>) field.get(classLoader); |
7 |
Iterator it = libs.iterator(); |
9 |
Object object = it.next(); |
10 |
Method finalize = object.getClass().getDeclaredMethod( "finalize" ); |
11 |
finalize.setAccessible( true ); |
12 |
finalize.invoke(object); |
14 |
} catch (Throwable th) { |
19 |
public static synchronized void unloadNativeLibs(String libName) { |
21 |
ClassLoader classLoader = Foo. class .getClassLoader(); |
22 |
Field field = ClassLoader. class .getDeclaredField( "nativeLibraries" ); |
23 |
field.setAccessible( true ); |
24 |
Vector<Object> libs = (Vector<Object>) field.get(classLoader); |
25 |
Iterator it = libs.iterator(); |
26 |
while (it.hasNext()) { |
27 |
Object object = it.next(); |
28 |
Field[] fs = object.getClass().getDeclaredFields(); |
29 |
for ( int k = 0 ; k < fs.length; k++) { |
30 |
if (fs[k].getName().equals( "name" )) { |
31 |
fs[k].setAccessible( true ); |
32 |
String dllPath = fs[k].get(object).toString(); |
33 |
if (dllPath.endsWith(libName)) { |
34 |
Method finalize = object.getClass().getDeclaredMethod( "finalize" ); |
35 |
finalize.setAccessible( true ); |
36 |
finalize.invoke(object); |
41 |
} catch (Throwable th) { |
【注】unloadNativeLibs(String libName)这个 libName 不是那个“foo.dll”,而是一个“foo”+Long类型的随机数+“.dll”。