分享

Linux下动态共享库加载时遇到的问题解决方案及原理

 guitarhua 2016-06-06

在java调用jni或者JNA时候,报

      error while loading shared libraries: libprint.so: cannot open shared object file: No such file or directory。这便是典型的找不到动态库的错误.

 

具体说来,动态链接器ld.so按照下面的顺序来搜索需要的动态共享库:

  1.ELF可执行文件中动态段中DT_RPATH所指定的路径。这实际上是通过一种不算很常用,却比较实用的方法所设置的:编译目标代码时,可以对gcc加入链接参数“-Wl,-rpath”指定动态库搜索路径;

  2.环境变量LD_LIBRARY_PATH指定的动态库搜索路径;

  3./etc/ld.so.cache中所缓存的动态库路径(如果支持ld.so.cache的话)。这可以通过修改配置文件/etc/ld.so.conf中指定的动态库搜索路径来改变;

  4.默认的动态库搜索路径/lib;

  5.默认的动态库搜索路径/usr/lib。

  在嵌入式Linux系统的实际应用中,1和2被经常使用,也有一些相对简单的的嵌入式系统会采用4或5的路径来规范动态库。3在嵌入式系统中使用的比较少,因为有很多系统根本就不支持ld.so.cache。

  4和5的方式非常简单,只要将所需要的库放到/lib或/usr/lib就可以解决找不到库的问题,不过对于大一些的系统来说,不太方便管理。1和2的方式要稍微复杂一些。

 

 

         项目中使用到JNI来加载本地库,项目自身(web使用)和对外接口(不是web使用)都用到相同的DLL,当web使用访问正常时,接口访问就报错;相反当接口访问正常时,web使用也会报错。错误信息:java.lang.UnsatisfiedLinkError: Native Library XX.dll already loaded in another。

 

分析:这种错误在我们使用热启动方式发布某个使用了JNI技术的Web应用时,并将调用年native方法的jar包独立部署在该应用下面,当我们的Web应用有了更新以后,在调用到该jar包封装的native方法时,会抛出该错误。(以上OS为Windows,若是Linux或Unix,应该是xxx.so 报错) 这是因为Web服务器已经在第一次加载该应用时,已经load了该dll,当该应用被再次热启动时,该dll将重新被加载,于是报错。解决方案: 一、将含有JNI调用的jar包部署在Web服务器的公用lib库中。Web应用再发布时可以不用加载;二、jar包部署不变,在该Web中实现一个listener,监听是否第一次启动,若不是第一次启动,屏蔽掉该jar包所含dll的加载。

 

1、原因

        查询资料后才得知:Java虚拟机为了在JNI本地库中确保基于classloader的命名空间隔离,因而不允许一个JNI本地库被两个不同的classloader加载。而JBoss中web应用的classloader是独立的,也就是说每个web应用都有一个专属的classloader,这样就出现两个classloader加载同一JNI本地库的情况。

 

 

 

2、解决方法

         解决这个问题其实很简单,将访问到jni的代码单独提取出来,并不直接让项目自身的classLoader加载,则是让其由systemLoader加载 即可。一种方法就是将这部分代码,单独封装成一个jar,放到java的systemLoader可以加载的地方,如lib/ext目录下。然后,项目中 仍然去调用此代码。由于访问dll的代码由systemLoader加载,因此,多个项目同时访问同一个dll时,即可避免再次加载了。因为,第二个项目 在访问时,寻找到的类,已经被systemLoader加载过了,因此项目本身的classLoader就不会再去加载这个类了。

        在Weblogic中,虽然不同的web应用使用不同的classloader,但是web应用classloader的父classloader是相同的,这样根据双亲委托模型只要让父classloader加载JNI本地库就可以避免被多个classloader加载。将so放在共享目录(System.getProperty("java.library.path"))的lib目录的并采用System.loadLibrary(libName)即可。

重新Weblogic问题就可以解决。

 

HPUX环境,在WebLogic服务器中使用java调用C动态连接库异常问题总结

 

总结1:查看服务器环境变量是否设置

       SHLIB_PATH此环境变量设置为HPUX默认的查找路径,配置为动态连接库的路径。

       LD_LIBRARY_PATH此为LINUX设置的路径。

 

注意:

       此环境变量不一定非得设置,可以用java.library.path来查看当前的路径指向哪里,可以将动态连接库拷到java.library.path指定的目录即可。

 

总结2:查看so文件是否有启动权限,如果没有则会报权限问题。

       测试方法:可以使用System.loadLibrary(“XXXXX.so”)来进行测试,如果正常通过则无异常显示,如果有异常,则根据提示来进行相应的更改。

       测试时会提示是不在路径中,还是别的什么异常。

 

 基础知识:

UnsatisfiedLinkError

在把本机调用链接到对应的本机定义时,类装入器扮演着重要角色。如果程序试图装入一个不存在或者放错的本机库时,在链接阶段的解析过程会发生 UnsatisfiedLinkError。JVM 规范指定 UnsatisfiedLinkError 是:

对于声明为 native 的方法,如果 Java 虚拟机找不到和它对应的本机语言定义,就会抛出该异常。 当调用本机方法时,类装入器会尝试装入定义了该方法的本机库。如果找不到这个库,就会抛出这个错误。

本机库的装入由调用 System.loadLibrary() 方法的类的类装入器启动 —— 在清单 6 中,就是 UnsatisfiedLinkErrorTest 的类装入器。根据使用的类装入器,会搜索不同的位置:

  • 对于由 bootstrap 类装入器装入的类,搜索 sun.boot.library.path
  • 对于由扩展类装入器装入的类,先搜索 java.ext.dirs,然后是 sun.boot.library.path,然后是 java.library.path
  • 对于由系统类装入器装入的类,搜索 sun.boot.library.path,然后是 java.library.path

在清单 6 中,UnsatisfiedLinkErrorTest 类是由系统类装入器装入的。要装入所引用的本机库,这个类装入器先查找 sun.boot.library.path,然后查找 java.library.path。因为在两个位置中都没有需要的库,所以类装入器抛出 UnsatisfiedLinkageError

 

System.loadLibrary()的使用方法汇总

当使用System.loadLibrary()调用 Dll,两种方法:

1.设定环境变量。

比如:所编辑的Dll在目录“D:/cppProjects/nativecode/release”内,将这个路径复制添加到电脑的环境变量中的path变量内即可。

2.设定项目属性。(开发推荐)

右击项目名|选择属性properties|在左边列表内选择“Java Build Path”|在右边选项卡用选择“source”|点开项目名前的“+”号,选择“Native library location”,“Edit”选择上面“D:/cppProjects/nativecode/release”路径。(当然如果将dll拷贝到workspace下也可以用相对路径。也可右击“src”设定其properties内Native Library项。)

 

 

System.load 和 System.loadLibrary详解

1.它们都可以用来装载库文件,不论是JNI库文件还是非JNI库文件。在任何本地方法被调用之前必须先用这个两个方法之一把相应的JNI库文件装载。

2.System.load 参数为库文件的绝对路径,可以是任意路径。
例如你可以这样载入一个windows平台下JNI库文件:
System.load("C://Documents and Settings//TestJNI.dll");。

3. System.loadLibrary 参数为库文件名,不包含库文件的扩展名。
例如你可以这样载入一个windows平台下JNI库文件
System. loadLibrary ("TestJNI");

这里,TestJNI.dll 必须是在java.library.path这一jvm变量所指向的路径中。
可以通过如下方法来获得该变量的值:
System.getProperty("java.library.path");
默认情况下,在Windows平台下,该值包含如下位置:
1)和jre相关的一些目录
2)程序当前目录
3)Windows目录
4)系统目录(system32)
5)系统环境变量path指定目录

4.如果你要载入的库文件静态链接到其它动态链接库,例如TestJNI.dll 静态链接到dependency.dll, 那么你必须注意:
1)如果你选择
System.load("C://Documents and Settings// TestJNI.dll");
那么即使你把dependency.dll同样放在C://Documents and Settings//下,load还是会因为找不到依赖的dll而失败。因为jvm在载入TestJNI.dll会先去载入TestJNI.dll所依赖的库文件dependency.dll,而dependency.dll并不位于java.library.path所指定的目录下,所以jvm找不到dependency.dll。
你有两个方法解决这个问题:一是把C://Documents and Settings//加入到java.library.path的路径中,例如加入到系统的path中。二是先调用
System.load("C://Documents and Settings// dependency.dll"); 让jvm先载入dependency.dll,然后再调用System.load("C://Documents and Settings// TestJNI.dll");
2)如果你选择
System. loadLibrary ("TestJNI");
那么你只要把dependency.dll放在任何java.library.path包含的路径中即可,当然也包括和TestJNI.dll相同的目录。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多