Java loadlibrary分析及如何unload

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中的动态链接库

1 // 系统自己会判断扩展名是dll还是so
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,

  1. 拷贝到classpath中,用System.loadLibrary(name)加载;
  2. 如果没有权限拷贝到指定目录,也可以拷贝到temp目录中,用System.load(path)加载。

1. 拷贝到classpath中,用System.loadLibrary(name)加载

1 static {
2   InputStream in = null;
3   FileOutputStream out = null;
4   try {
5     String libpath = System.getProperty("java.library.path");
6     if (libpath == null || libpath.length() == 0)
7       throw new RuntimeException("java.library.path is null");
8     String path = null;
9     String pathSeparator = System.getProperty("path.separator");
10     StringTokenizer st = new StringTokenizer(libpath, pathSeparator);
11     if (st.hasMoreElements())
12       path = st.nextToken();
13     else
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];
19     int len;
20     while ((len = in.read(buffer)) != -1)
21       out.write(buffer, 0, len);
22     out.close();
23     fooDll.deleteOnExit();
24     System.loadLibrary("foo");
25   } catch (Throwable e) {
26     e.printStackTrace();
27   } finally {
28     // 流的判空和关闭
29   }
30 }

2. 拷贝到temp目录中,用System.load(path)加载

1 static {
2   InputStream in = null;
3   FileOutputStream out = null;
4   try {
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];
9     int len;
10     while ((len = in.read(buffer)) != -1)
11       out.write(buffer, 0, len);
12     out.close();
13     temporaryDll.deleteOnExit();
14     System.load(temporaryDll.getPath());
15   } catch (Throwable e) {
16     e.printStackTrace();
17   } finally {
18     // 流的判空和关闭
19   }
20 }

为什么上面通过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() {
2   public void run() {
3     // unloadAllNativeLibs();
4     unloadNativeLibs(temporaryDll.getName());
5   }
6 });

那么如何在程序推出时卸载动态库文件呢?可以通过反射调用私有属性和私有方法来卸载:

1 public static synchronized void unloadAllNativeLibs() {
2   try {
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();
8     while (it.hasNext()) {
9       Object object = it.next();
10       Method finalize = object.getClass().getDeclaredMethod("finalize");
11       finalize.setAccessible(true);
12       finalize.invoke(object);
13     }
14   } catch (Throwable th) {
15     th.printStackTrace();
16   }
17 }
18  
19 public static synchronized void unloadNativeLibs(String libName) {
20   try {
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);
37           }
38         }
39       }
40     }
41   } catch (Throwable th) {
42     th.printStackTrace();
43   }
44 }

【注】unloadNativeLibs(String libName)这个 libName 不是那个“foo.dll”,而是一个“foo”+Long类型的随机数+“.dll”。


你可能感兴趣的:(java,loadlibrary,Unload)