解析java.library.path和LD_LIBRARY_PATH的介绍与区别

背景

近期要将算法部署到一个机群的虚拟主机(Debian 9.1 gcc 6.3.0)上,采用的是Java + JNI + shared library的方式来完成底层算法能力的部署。

其中需要用到各种第三方库,有从源码编译的,也有直接下载的so,包括OpenCV相关、TensorFlow相关、MKL以OpenMP相关的动态库。

遇到一个问题,libmklml_intel.so 这个库只能放在 LD_LIBRARY_PATH中进行加载,而不能通过java.library.path完成加载,所以有必要搞清楚这两个路径究竟有什么区别。

java.library.path

官方文档的定义是:List of paths to search when loading libraries
从定义我们可以发现,首先是一个list,也就是说可以包括多个地址,然后这些地址是用来帮助jvm搜索需要加载的库文件的。

设置java.library.path

最简单的办法就是在启动jvm前通过java -Djava.library.path=path-to-your-libs设置这个全局变量。

作用

那么这个地址具体是如何被使用的呢?
当我们调用System.loadLibrary(libname)时,会调用Runtime.loadLibary,然后调用java/lang/ClassLoader.loadLibrary。在ClassLoader.loadLibrary中,系统属性java.library.path将会被获取,并用来生成需要加载的库的绝对路径,然后将这个绝对路径传给本地方法来调用dlopen/dlsym并最终加载这个库。
如果加载失败,会根据实际情况返回三个异常值:

SecurityException − if a security manager exists and its checkLink method doesn't allow loading of the specified dynamic library
UnsatisfiedLinkError − if the library does not exist
NullPointerException − if libname is null

可以参考OpenJDK的仓库:

static void loadLibrary(Class fromClass, String name,
                            boolean isAbsolute) {
        ClassLoader loader =
            (fromClass == null) ? null : fromClass.getClassLoader();
        if (sys_paths == null) {
            usr_paths = initializePath("java.library.path");
            sys_paths = initializePath("sun.boot.library.path");
        }
        if (isAbsolute) {
            if (loadLibrary0(fromClass, new File(name))) {
                return;
            }
            throw new UnsatisfiedLinkError("Can't load library: " + name);
        }
        if (loader != null) {
            String libfilename = loader.findLibrary(name);
            if (libfilename != null) {
                File libfile = new File(libfilename);
                if (!libfile.isAbsolute()) {
                    throw new UnsatisfiedLinkError(
    "ClassLoader.findLibrary failed to return an absolute path: " + libfilename);
                }
                if (loadLibrary0(fromClass, libfile)) {
                    return;
                }
                throw new UnsatisfiedLinkError("Can't load " + libfilename);
            }
        }
        for (int i = 0 ; i < sys_paths.length ; i++) {
            File libfile = new File(sys_paths[i], System.mapLibraryName(name));
            if (loadLibrary0(fromClass, libfile)) {
                return;
            }
        }
        if (loader != null) {
            for (int i = 0 ; i < usr_paths.length ; i++) {
                File libfile = new File(usr_paths[i],
                                        System.mapLibraryName(name));
                if (loadLibrary0(fromClass, libfile)) {
                    return;
                }
            }
        }
        // Oops, it failed
        throw new UnsatisfiedLinkError("no " + name + " in java.library.path");
    }

LD_LIBRARY_PATH

为了搞清楚这个变量的作用,我们先说明一下Unix系统是如何加载动态库的,然后自然就明白为什么要有LD_LIBRARY_PATH以及如何使用了。

动态库如何加载?

在基于GNU glibc的系统上,包括所有的linux系统,启动一个ELF格式的二进制可执行文件会自动调用加载器加载必要的动态链接库,一个最简单的可执行文件一般也会包含一些系统的动态库比如libc.so等。在Linux系统中,这个加载器叫做/lib/ld-linux.so.X,这个X指的是加载器的版本号。加载器然后查找并加载所需的动态库。

加载器在什么路径中搜索和加载动态库呢——/etc/ld.so.conf,这个文件会包括/etc/ld.so.conf.d/*.conf这些文件夹中所有的.conf文件,而具体的动态库搜索路径,就包含在每个.conf文件中,比如/etc/ld.so.conf.d/libc.conf,它是libc的默认的搜索路径/usr/local/lib,这也是为什么我们不需要显示声明使用系统库却能自动完成加载的原因,也是为什么不同的系统编出来的库无法通用的可见原因之一,因为不同系统的/usr/local/lib目录下的动态库并不一致。

如果每次启动都去查找所有的目录,那样显然是比较笨的做法,所以使用/etc/ld.so.cache来缓存路径,并通过ldconfig来更新这个缓存路径,有兴趣的可以自行查看一下这个缓存文件。实际上,这个缓存路径也很长了,基本上包含了系统可能存放动态库的路径。

为什么有LD_LIBRARY_PATH?

上面我们说到可以通过cache和ldconfig来简化搜索和加载动态库的流程,但是还有两个问题没有考虑到,一是还没有将编出来的库放到系统目录中去,二是依赖库数量很少,不需要经过这么复杂的查找。

LD_LIBRARY_PATH就是用来满足这个需要,它也指定一个搜索路径,且ld-linux.so会优先在这个路径下搜索需要的动态库,如果没找到,再去ld.so.conf中指定的目录寻找。

使用

export LD_LIBRARY_PATH=paths-to-libs

需要注意的一点是,多个目录是通过:隔开的

区别

前面分别介绍了java.library.path 和 LD_LIBRARY_PATH,都是为了加载所需的动态库,有什么区别呢?

前者是在java环境中调用,在jvm启动前设置生效;后者也是在启动前,但是是在Unix环境中使用前者是通过修改property来设置路径;后者是直接增加了ld-linux.so的搜索路径对于JNI直接调用的库,最好使用前者,对于有多重依赖关系的库,最好使用LD_LIBRARY_PATH

参考

HowTo: How to configure library path for JNI dependent libraries
https://zauner.nllk.net/post/0013-jni-and-the-java-library-path/
https://docs.oracle.com/javase/8/docs/api/java/lang/System.html#getProperties–
https://www.tutorialspoint.com/java/lang/runtime_loadlibrary.htm
https://stackoverflow.com/questions/27945268/difference-between-using-java-library-path-and-ld-library-path
Linux关于动态库的文档

到此这篇关于解析java.library.path和LD_LIBRARY_PATH的介绍与区别的文章就介绍到这了,更多相关java.library.path和LD_LIBRARY_PATH内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • environments was not found on the java.library.path 问题的解决方法

    Tomcat 启动出现信息如下: 信息: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path:XXXX 出现原因分析: Tomcat建议使用apache的apr,来更好的运行Tomcat; apache的apr 的介绍 APR(Apache portable Run-

  • 解析java.library.path和LD_LIBRARY_PATH的介绍与区别

    背景 近期要将算法部署到一个机群的虚拟主机(Debian 9.1 gcc 6.3.0)上,采用的是Java + JNI + shared library的方式来完成底层算法能力的部署. 其中需要用到各种第三方库,有从源码编译的,也有直接下载的so,包括OpenCV相关.TensorFlow相关.MKL以OpenMP相关的动态库. 遇到一个问题,libmklml_intel.so 这个库只能放在 LD_LIBRARY_PATH中进行加载,而不能通过java.library.path完成加载,所以有

  • 解析Java和Eclipse中加载本地库(.dll文件)的详细说明

    最近在做的工作要用到本地方法,需要在Java中加载不少动态链接库(以下为方便延用Windows平台下的简写dll,但并不局限于Windows).刚刚把程序跑通,赶紧把一些心得写出来,mark.也希望对大家的类似工作有所帮助首先,应当明确,dll有两类:(1)Java所依赖的dll和,(2)dll所依赖的dll.正是由于第(2)种dll的存在,才导致了java中加载dll的复杂性大大增加,许多说法都是这样的,但我实验的结果却表明似乎没有那么复杂,后面会予以详细阐述.其次,Java中加载dll的方式

  • Java 对象序列化 NIO NIO2详细介绍及解析

    Java 对象序列化 NIO NIO2详细介绍及解析 概要: 对象序列化 对象序列化机制允许把内存中的Java对象转换成与平台无关的二进制流,从而可以保存到磁盘或者进行网络传输,其它程序获得这个二进制流后可以将其恢复成原来的Java对象. 序列化机制可以使对象可以脱离程序的运行而对立存在 序列化的含义和意义 序列化 序列化机制可以使对象可以脱离程序的运行而对立存在 序列化(Serialize)指将一个java对象写入IO流中,与此对应的是,对象的反序列化(Deserialize)则指从IO流中恢

  • JDK的Parser来解析Java源代码详解

    在JDK中,自带了一套相关的编译API,可以在Java中发起编译流程,解析Java源文件然后获取其语法树,在JDK的tools.jar(OSX下可以在/Library/Java/JavaVirtualMachines/jdk_version/Contents/Home/lib中找到)中包含着这整套API,但是这却不是Oracle和OpenJDK发布中的公开API,因此对于这套API,并没有官方的正式文档来进行说明.但是,也有不少项目利用了这套API来做了不少事情,例如大名鼎鼎的lombok使用了

  • 深入解析Java中的Class Loader类加载器

    类加载的过程 类加载器的主要工作就是把类文件加载到JVM中.如下图所示,其过程分为三步: 1.加载:定位要加载的类文件,并将其字节流装载到JVM中: 2.链接:给要加载的类分配最基本的内存结构保存其信息,比如属性,方法以及引用的类.在该阶段,该类还处于不可用状态: (1)验证:对加载的字节流进行验证,比如格式上的,安全方面的: (2)内存分配:为该类准备内存空间来表示其属性,方法以及引用的类: (3)解析:加载该类所引用的其它类,比如父类,实现的接口等. 3.初始化:对类变量进行赋值. 类加载器

  • 解析Java和IDEA中的文件打包问题

    问题:想在IDEA中引用相对路径,但是找不到文件. 项目目录结构 当前项目的路径为:D:\source\java\test\ 项目结构如下 当前路径 面对无法使用相对路径找到资源文件的问题,首先想到的解决办法是先瞄一眼IDEA在执行时给Java环境设定的当前路径在哪,也就是说看看我们在使用相对路径时到底是相对于哪里的. 应该咋写呢?下面是Java API中的一些描述. 默认情况下, java.io包中的类始终会根据当前用户目录解析相对路径名. 该目录由系统属性user.dir ,通常是调用Jav

  • java spring mvc处理器映射器介绍

    目录 一.RequestMappingHandlerMapping解析映射简单介绍 二.@RequestMapping解析源码流程 三.@RequestMapping映射源码流程 四.@RequestMapping解析源码 五.@RequestMapping映射源码 前言: 本文源码基于spring-framework-5.3.10. mvc是spring源码中的一个子模块! 一.RequestMappingHandlerMapping解析映射简单介绍 @RequestMapping通过Requ

  • 源码解析Java类加载器

    参考内容: 深入理解Java虚拟机(JVM高级特性与最佳实践) --周志明老师 尚硅谷深入理解JVM教学视频--宋红康老师 我们都知道Java的类加载器结构为下图所示(JDK8及之前,JDK9进行了模块化): 关于三层类加载器.双亲委派机制,本文不再板书,读者可自行百度. 那么在JDK的源码中,三层结构的具体实现是怎么样的呢? Bootstrap ClassLoader(引导类加载器) 引导类加载器是由C++实现的,并非Java代码实现,所以在Java代码中是无法获取到该类加载器的. 一般大家都

  • Java Spring-Cache key配置注意事项介绍

    为了提升项目的并发性能,考虑引入本地内存Cache,对:外部数据源访问.Restful API调用.可重用的复杂计算 等3种类型的函数处理结果进行缓存.目前采用的是spring Cache的@Cacheable注解方式,缓存具体实现选取的是Guava Cache. 具体缓存的配置此处不再介绍,重点对于key的配置进行说明: 1.基本形式 @Cacheable(value="cacheName", key"#id") public ResultDTO method(i

随机推荐