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
2System.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)加载

1static {
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)加载

1static {
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),当然会找不到文件。

1URL url = Foo.class.getResource("/java/lang/String.class");
2System.out.println(url);
3System.out.println(url.toExternalForm());
4System.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,让程序退出时卸载动态链接库:

1Runtime.getRuntime().addShutdownHook(new Thread() {
2  public void run() {
3    // unloadAllNativeLibs();
4    unloadNativeLibs(temporaryDll.getName());
5  }
6});

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

1public 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 
19public 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”。


  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值