解析java.library.path和LD_LIBRARY_PATH的介紹與區(qū)別
背景
近期要將算法部署到一個(gè)機(jī)群的虛擬主機(jī)(Debian 9.1 gcc 6.3.0)上,采用的是Java + JNI + shared library的方式來完成底層算法能力的部署。
其中需要用到各種第三方庫(kù),有從源碼編譯的,也有直接下載的so,包括OpenCV相關(guān)、TensorFlow相關(guān)、MKL以O(shè)penMP相關(guān)的動(dòng)態(tài)庫(kù)。
遇到一個(gè)問題,libmklml_intel.so 這個(gè)庫(kù)只能放在 LD_LIBRARY_PATH中進(jìn)行加載,而不能通過java.library.path完成加載,所以有必要搞清楚這兩個(gè)路徑究竟有什么區(qū)別。
java.library.path
官方文檔的定義是:List of paths to search when loading libraries
從定義我們可以發(fā)現(xiàn),首先是一個(gè)list,也就是說可以包括多個(gè)地址,然后這些地址是用來幫助jvm搜索需要加載的庫(kù)文件的。
設(shè)置java.library.path
最簡(jiǎn)單的辦法就是在啟動(dòng)jvm前通過java -Djava.library.path=path-to-your-libs設(shè)置這個(gè)全局變量。
作用
那么這個(gè)地址具體是如何被使用的呢?
當(dāng)我們調(diào)用System.loadLibrary(libname)時(shí),會(huì)調(diào)用Runtime.loadLibary,然后調(diào)用java/lang/ClassLoader.loadLibrary。在ClassLoader.loadLibrary中,系統(tǒng)屬性java.library.path將會(huì)被獲取,并用來生成需要加載的庫(kù)的絕對(duì)路徑,然后將這個(gè)絕對(duì)路徑傳給本地方法來調(diào)用dlopen/dlsym并最終加載這個(gè)庫(kù)。
如果加載失敗,會(huì)根據(jù)實(shí)際情況返回三個(gè)異常值:
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
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
為了搞清楚這個(gè)變量的作用,我們先說明一下Unix系統(tǒng)是如何加載動(dòng)態(tài)庫(kù)的,然后自然就明白為什么要有LD_LIBRARY_PATH以及如何使用了。
動(dòng)態(tài)庫(kù)如何加載?
在基于GNU glibc的系統(tǒng)上,包括所有的linux系統(tǒng),啟動(dòng)一個(gè)ELF格式的二進(jìn)制可執(zhí)行文件會(huì)自動(dòng)調(diào)用加載器加載必要的動(dòng)態(tài)鏈接庫(kù),一個(gè)最簡(jiǎn)單的可執(zhí)行文件一般也會(huì)包含一些系統(tǒng)的動(dòng)態(tài)庫(kù)比如libc.so等。在Linux系統(tǒng)中,這個(gè)加載器叫做/lib/ld-linux.so.X,這個(gè)X指的是加載器的版本號(hào)。加載器然后查找并加載所需的動(dòng)態(tài)庫(kù)。
加載器在什么路徑中搜索和加載動(dòng)態(tài)庫(kù)呢——/etc/ld.so.conf,這個(gè)文件會(huì)包括/etc/ld.so.conf.d/*.conf這些文件夾中所有的.conf文件,而具體的動(dòng)態(tài)庫(kù)搜索路徑,就包含在每個(gè).conf文件中,比如/etc/ld.so.conf.d/libc.conf,它是libc的默認(rèn)的搜索路徑/usr/local/lib,這也是為什么我們不需要顯示聲明使用系統(tǒng)庫(kù)卻能自動(dòng)完成加載的原因,也是為什么不同的系統(tǒng)編出來的庫(kù)無法通用的可見原因之一,因?yàn)椴煌到y(tǒng)的/usr/local/lib目錄下的動(dòng)態(tài)庫(kù)并不一致。
如果每次啟動(dòng)都去查找所有的目錄,那樣顯然是比較笨的做法,所以使用/etc/ld.so.cache來緩存路徑,并通過ldconfig來更新這個(gè)緩存路徑,有興趣的可以自行查看一下這個(gè)緩存文件。實(shí)際上,這個(gè)緩存路徑也很長(zhǎng)了,基本上包含了系統(tǒng)可能存放動(dòng)態(tài)庫(kù)的路徑。
為什么有LD_LIBRARY_PATH?
上面我們說到可以通過cache和ldconfig來簡(jiǎn)化搜索和加載動(dòng)態(tài)庫(kù)的流程,但是還有兩個(gè)問題沒有考慮到,一是還沒有將編出來的庫(kù)放到系統(tǒng)目錄中去,二是依賴庫(kù)數(shù)量很少,不需要經(jīng)過這么復(fù)雜的查找。
LD_LIBRARY_PATH就是用來滿足這個(gè)需要,它也指定一個(gè)搜索路徑,且ld-linux.so會(huì)優(yōu)先在這個(gè)路徑下搜索需要的動(dòng)態(tài)庫(kù),如果沒找到,再去ld.so.conf中指定的目錄尋找。
使用
export LD_LIBRARY_PATH=paths-to-libs
需要注意的一點(diǎn)是,多個(gè)目錄是通過
:隔開的
區(qū)別
前面分別介紹了java.library.path 和 LD_LIBRARY_PATH,都是為了加載所需的動(dòng)態(tài)庫(kù),有什么區(qū)別呢?
前者是在java環(huán)境中調(diào)用,在jvm啟動(dòng)前設(shè)置生效;后者也是在啟動(dòng)前,但是是在Unix環(huán)境中使用前者是通過修改property來設(shè)置路徑;后者是直接增加了ld-linux.so的搜索路徑對(duì)于JNI直接調(diào)用的庫(kù),最好使用前者,對(duì)于有多重依賴關(guān)系的庫(kù),最好使用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關(guān)于動(dòng)態(tài)庫(kù)的文檔
到此這篇關(guān)于解析java.library.path和LD_LIBRARY_PATH的介紹與區(qū)別的文章就介紹到這了,更多相關(guān)java.library.path和LD_LIBRARY_PATH內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java基于分治法實(shí)現(xiàn)的快速排序算法示例
這篇文章主要介紹了Java基于分治法實(shí)現(xiàn)的快速排序算法,結(jié)合實(shí)例形式分析了java基于分治法的快速排序相關(guān)實(shí)現(xiàn)技巧,代碼中備有較為詳細(xì)的注釋說明便于理解,需要的朋友可以參考下2017-12-12
SpringBoot實(shí)現(xiàn)定時(shí)任務(wù)動(dòng)態(tài)管理示例
這篇文章主要為大家介紹了SpringBoot實(shí)現(xiàn)定時(shí)任務(wù)動(dòng)態(tài)管理示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
springboot使用ThreadPoolTaskExecutor多線程批量插入百萬級(jí)數(shù)據(jù)的實(shí)現(xiàn)方法
這篇文章主要介紹了springboot利用ThreadPoolTaskExecutor多線程批量插入百萬級(jí)數(shù)據(jù),本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02
Java如何用一個(gè)統(tǒng)一結(jié)構(gòu)接收成員名稱不固定的數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了Java如何用一個(gè)統(tǒng)一結(jié)構(gòu)接收成員名稱不固定的數(shù)據(jù),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-11-11

