1. 程式人生 > >Java中System.loadLibrary() 的執行過程

Java中System.loadLibrary() 的執行過程

System.loadLibrary()是我們在使用Java的JNI機制時,會用到的一個非常重要的函式,它的作用即是把實現了我們在Java code中宣告的native方法的那個libraryload進來,或者load其他什麼動態連線庫。

算是處於好奇吧,我們可以看一下這個方法它的實現,即執行流程。(下面分析的那些code,來自於android 4.2.2 aosp版。)先看一下這個方法的code(在libcore/luni/src/main/java/java/lang/System.java這個檔案中):

?
1 2 3 4 5 6 7 8 9 10 11 12 13 /** * Loads and links the library with the specified name. The mapping of the
* specified library name to the full path for loading the library is * implementation-dependent. * * @param libName *            the name of the library to load. * @throws UnsatisfiedLinkError *             if the library could no<span style="color:#003399;"></span>t be loaded. */ public static
void loadLibrary(String libName) { Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader()); }

由上面的那段code,可以看到,它的實現非常簡單,就只是先呼叫VMStack.getCallingClassLoader()獲取到ClassLoader,然後再把實際要做的事情委託給了Runtime來做而已。接下來我們再看一下Runtime.loadLibrary()的實現(在libcore/luni/src/main/java/java/lang/Runtime.java這個檔案中):

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 /* * Loads and links a library without security checks. */ void loadLibrary(String libraryName, ClassLoader loader) { if (loader != null) { String filename = loader.findLibrary(libraryName); if (filename == null) { throw new UnsatisfiedLinkError("Couldn't load " + libraryName + " from loader " + loader + ": findLibrary returned null"); } String error = nativeLoad(filename, loader); if (error != null) { throw new UnsatisfiedLinkError(error); } return; } String filename = System.mapLibraryName(libraryName); List<String> candidates = new ArrayList<String>(); String lastError = null; for (String directory : mLibPaths) { String candidate = directory + filename; candidates.add(candidate); if (new File(candidate).exists()) { String error = nativeLoad(candidate, loader); if (error == null) { return; // We successfully loaded the library. Job done. } lastError = error; } } if (lastError != null) { throw new UnsatisfiedLinkError(lastError); } throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates); }

由上面的那段code,我們看到,loadLibrary()可以被看作是一個2步走的過程

  1. 獲取到library path。對於這一點,上面的那個函式,依據於所傳遞的ClassLoader的不同,會有兩種不同的方法。如果ClassLoader非空,則會利用ClassLoader的findLibrary()方法來獲取library的path。而如果ClassLoader為空,則會首先依據傳遞進來的library name,獲取到library file的name,比如傳遞“hello”進來,它的library file name,經過System.mapLibraryName(libraryName)將會是“libhello.so”;然後再在一個path list(即上面那段code中的mLibPaths)中查詢到這個library file,並最終確定library 的path。
  2. 呼叫nativeLoad()這個native方法來load library

這段code,又牽出幾個問題,首先,可用的library path都是哪些,這實際上也決定了,我們的so檔案放在哪些folder下,才可以被真正load起來?其次,在native層load library的過程,又實際做了什麼事情?下面會對這兩個問題,一一的作出解答。

系統的library path

我們由簡單到複雜的來看這個問題。先來看一下,在傳入的ClassLoader為空的情況(儘管我們知道,在System.loadLibrary()這個case下不會發生),前面Runtime.loadLibrary()的實現中那個mLibPaths的初始化的過程,在Runtime的建構函式中,如下:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 /** * Prevent this class from being instantiated. */ private Runtime(){ String pathList = System.getProperty("java.library.path", "."); String pathSep = System.getProperty("path.separator", ":"); String fileSep = System.getProperty("file.separator", "/"); mLibPaths = pathList.split(pathSep); // Add a '/' to the end so we don't have to do the property lookup // and concatenation later. for (int i = 0; i < mLibPaths.length; i++) { if (!mLibPaths[i].endsWith(fileSep)) { mLibPaths[i] += fileSep; } } }

可以看到,那個library path list實際上讀取自一個system property。那在android系統中,這個system property的實際內容又是什麼呢?dump這些內容出來,就像下面這樣:

?
1 2 3 05-11 07:51:40.974: V/QRCodeActivity(11081): pathList = /vendor/lib:/system/lib 05-11 07:51:40.974: V/QRCodeActivity(11081): pathSep = : 05-11 07:51:40.974: V/QRCodeActivity(11081): fileSep = /

然後是傳入的ClassLoader非空的情況,ClassLoaderfindLibrary()方法的執行過程。首先看一下它的實現(在libcore/luni/src/main/java/java/lang/ClassLoader.java這個檔案中)

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 /** * Returns the absolute path of the native library with the specified name, * or {@code null}. If this method returns {@code null} then the virtual * machine searches the directories specified by the system property * "java.library.path". * <p> * This implementation always returns {@code null}. * </p> * * @param libName *            the name of the library to find. * @return the absolute path of the library. */ protected String findLibrary(String libName) { return null; }

竟然是一個空函式。那系統中實際執行的ClassLoader就是這個嗎?我們可以做一個小小的實驗,列印系統中實際執行的ClassLoader的String

?
1 2 ClassLoader classLoader = getClassLoader(); Log.v(TAG, "classLoader = " + classLoader.toString());
在Galaxy Nexus上執行的結果如下: ?
1 05-11 08:18:57.857: V/QRCodeActivity(11556): classLoader = dalvik.system.PathClassLoader[dexPath=/data/app/com.qrcode.qrcode-1.apk,libraryPath=/data/app-lib/com.qrcode.qrcode-1]
看到了吧,android系統中的 ClassLoader真正的實現 在dalvik的dalvik.system.PathClassLoader。開啟libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java來看 PathClassLoader這個class 的實現,可以看到,就只是簡單的繼承 BaseDexClassLoader而已,沒有任何實際的內容 。接下來我們就來看一下 BaseDexClassLoader中 那個 findLibrary() 真正的實現( 在libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java這個檔案中 ): ?
1 2 3 4 @Override public String findLibrary(String name) { return pathList.findLibrary(name); }

這個方法看上去倒挺簡單,不用多做解釋。然後來看那個pathList的初始化的過程,在BaseDexClassLoader的建構函式裡

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 /** * Constructs an instance. * * @param dexPath the list of jar/apk files containing classes and * resources, delimited by {@code File.pathSeparator}, which * defaults to {@code ":"} on Android * @param optimizedDirectory directory where optimized dex files * should be written; may be {@code null} * @param libraryPath the list of directories containing native * libraries, delimited by {@code File.pathSeparator}; may be * {@code null} * @param parent the parent class loader */ public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) { super(parent); this.originalPath = dexPath; this.originalLibraryPath = libraryPath; this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); }

BaseDexClassLoader的建構函式也不用多做解釋吧。然後是DexPathList的建構函式:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 /** * Constructs an instance. * * @param definingContext the context in which any as-yet unresolved * classes should be defined * @param dexPath list of dex/resource path elements, separated by * {@code File.pathSeparator} * @param libraryPath list of native library directory path elements, * separated by {@code File.pathSeparator} * @param optimizedDirectory directory where optimized {@code .dex} files * should be found and written to, or {@code null} to use the default * system directory for same */ public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) { if (definingContext == null) { throw new NullPointerException("definingContext == null"); } if (dexPath == null) { throw new NullPointerException("dexPath == null"); } if (optimizedDirectory != null) { if (!optimizedDirectory.exists())  { throw new IllegalArgumentException( "optimizedDirectory doesn't exist: " + optimizedDirectory); } if (!(optimizedDirectory.canRead() && optimizedDirectory.canWrite())) { throw new IllegalArgumentException( "optimizedDirectory not readable/writable: " + optimizedDirectory); } } this.definingContext = definingContext; this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory); this.nativeLibraryDirectories = splitLibraryPath(libraryPath); }

關於我們的library path的問題,可以只關注最後的那個splitLibraryPath(),這個地方,實際上即是把傳進來的libraryPath 又丟給splitLibraryPath來獲取library path 的list。可以看一下DexPathList.splitLibraryPath()的實現:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 /** * Splits the given library directory path string into elements * using the path separator ({@code File.pathSeparator}, which * defaults to {@code ":"} on Android, appending on the elements * from the system library path, and pruning out any elements that * do not refer to existing and readable directories. */ private static File[] splitLibraryPath(String path) { /* * Native libraries may exist in both the system and * application library paths, and we use this search order: * *   1. this class loader's library path for application *      libraries *   2. the VM's library path from the system *      property for system libraries * * This order was reversed prior to Gingerbread; see http://b/2933456. */ ArrayList<File> result = splitPaths( path, System.getProperty("java.library.path", "."), true); return result.toArray(new File[result.size()]); }

這個地方,是在用兩個部分的library path list來由splitPaths構造最終的那個path list,一個部分是,傳進來的library path,另外一個部分是,像我們前面看到的那個,是system property。然後再來看一下DexPathList.splitPaths()的實現:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /** * Splits the given path strings into file elements using the path * separator, combining the results and filtering out elements * that don't exist, aren't readable, or aren't either a regular * file or a directory (as specified). Either string may be empty * or {@code null}, in which case it is ignored. If both strings * are empty or {@code null}, or all elements get pruned out, then * this returns a zero-element list. */ private static ArrayList<File> splitPaths(String path1, String path2, boolean wantDirectories) { ArrayList<File> result = new ArrayList<File>(); splitAndAdd(path1, wantDirectories, result); splitAndAdd(path2, wantDirectories, result); return result; }

總結一下,ClassLoader的那個findLibrary()實際上會在兩個部分的folder中去尋找System.loadLibrary()要load的那個library,一個部分是,構造ClassLoader時,傳進來的那個library path,即是app folder,另外一個部分是system property。在android系統中,查詢要load的library,實際上會在如下3個folder中進行:

  1. /vendor/lib
  2. /system/lib
  3. /data/app-lib/com.qrcode.qrcode-1

上面第3個item只是一個例子,每一個app,它的那個app library path的最後一個部分都會是特定於那個app的。至於說,構造BaseDexClassLoader時的那個libraryPath 到底是怎麼來的,那可能就會牽扯到android本身更復雜的一些過程了,在此不再做更詳細的說明。

Native 層load library的過程

然後來看一下native層,把so檔案load起的過程,先來一下nativeLoad()這個函式的實現(在JellyBean/dalvik/vm/native/java_lang_Runtime.cpp這個檔案中):

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 /* * static String nativeLoad(String filename, ClassLoader loader) * * Load the specified full path as a dynamic library filled with * JNI-compatible methods. Returns null on success, or a failure * message on failure. */ static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args, JValue* pResult) { StringObject* fileNameObj = (StringObject*) args[0]; Object* classLoader = (Object*) args[1]; char* fileName = NULL; StringObject* result = NULL; char* reason = NULL; bool success; assert(fileNameObj != NULL); fileName = dvmCreateCstrFromString(fileNameObj); success = dvmLoadNativeCode(fileName, classLoader, &reason); if (!success) { const char* msg = (reason != NULL) ? reason : "unknown failure"; result = dvmCreateStringFromCstr(msg); dvmReleaseTrackedAlloc((Object*) result, NULL); } free(reason); free(fileName); RETURN_PTR(result); }

可以看到,nativeLoad()實際上只是完成了兩件事情,第一,是呼叫dvmCreateCstrFromString()將Java 的library path String 轉換到native的String,然後將這個path傳給dvmLoadNativeCode()做load,dvmLoadNativeCode()這個函式的實現在dalvik/vm/Native.cpp中,如下:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 /* * Load native code from the specified absolute pathname.  Per the spec, * if we've already loaded a library with the specified pathname, we

相關推薦

JavaSystem.loadLibrary() 的執行過程

System.loadLibrary()是我們在使用Java的JNI機制時,會用到的一個非常重要的函式,它的作用即是把實現了我們在Java code中宣告的native方法的那個libraryload進來,或者load其他什麼動態連線庫。 算是處於好奇吧,我們可以看一下這個方法它的實現,即執行流程。(下面

javaSystem.getProperty()的作用及使用

south pro name watermark sta tin fontsize sun copyto Java中給我們提供了System.getProperty()這個函數,這個函數可以獲取到Java JVM以及操作系統的一些參數,可以供程序判斷等。 System.ge

pythonprint()函數的“,”與javaSystem.out.print()函數的“+”

兩個 java 新特性 highlight 不同 連接 .py sys pre python中的print()函數和java中的System.out.print()函數都有著打印字符串的功能。 python中: print("hello,world!") 輸出結果為:h

java System

public static void systemTest() { Map<String, String> getenv = System.getenv(); for (Map.Entry<String, String> kvp :

Java的多執行緒你只要看這一篇就夠了(轉)

引 如果對什麼是執行緒、什麼是程序仍存有疑惑,請先Google之,因為這兩個概念不在本文的範圍之內。 用多執行緒只有一個目的,那就是更好的利用cpu的資源,因為所有的多執行緒程式碼都可以用單執行緒來實現。說這個話其實只有一半對,因為反應“多角色”的程式程式碼,最起碼每個角色要給他一個執行緒吧,否

關於java限定方法執行時間淺析

前兩天專案的測試要求在專案在執行檢索和提交圖片兩個方法,加上一個時間限制來限制方法的執行時間。到時間後要彈出一個提示框,當時聽到這個要求之後心裡有兩個方案,一個是在後臺用定時任務(比較麻煩)。不到山窮水盡時不考慮。另一個是在前端js加個定時器(window.setInterval),但是我們都知道前

java常見的執行緒池(不看後悔,一看必懂)

Executor介面表示執行緒池,它的execute(Runnable task)方法用來執行Runnable型別的任務,ExecutorService是Executor的子介面,聲明瞭管理執行緒池的一些方法 Java.util.concurrent.Executors類包含了一些靜態

JavaSystem

1、 System類包含一些有用的類欄位和方法,他不能被例項化 2、 方法:  a) public static void gc()   i. 用於垃圾回收,系統自動呼叫finalize()方法清除物件佔有資源,每次呼叫垃圾回收都會強制啟動垃圾回收器,會耗費更多資源,所以在大量物件釋放時再呼

Java的方法呼叫過程分析

假設呼叫x.f(args),隱式引數x宣告為類C的一個例項物件: 1.編譯器檢視物件的宣告型別和方法名。例如,可能存在方法f(int)和方法f(String)。編譯器將會一一列舉出所有該類中名為f的方法和其超類中訪問屬性為public且名為f的方法。 2.編譯器將檢視呼叫方法時提供的引數型別

#圖文詳解:從實際和理論出發,帶你瞭解Java的多執行

這裡並沒有講什麼新東西,只是把多執行緒一些知識來個總結。大家懂得可以複習複習,還有些童鞋對多執行緒朦朧的可以拿這個做為入門~ 舉個栗子說明啥是多執行緒:玩遊戲,前面一堆怪,每個怪都是一個執行緒,你射了一槍,子彈飛出去了,這顆子彈也是一個執行緒。你開啟你的程序管理,看到你遊戲的後臺程序,這就是程序

java的多執行緒 // 基礎

java 中的多執行緒 簡介   程序 : 指正在執行的程式,並具有一定的獨立能力,即 當硬碟中的程式進入到記憶體中執行時,就變成了一個程序   執行緒 : 是程序中的一個執行單元,負責當前程式的執行。執行緒就是CPU通向程式的路徑        一個程序中只有一個執行緒,單執行緒程式  

javaSystem.getProperty()和System.setProperty()和System.getProperties()

System可以有對標準輸入,標準輸出,錯誤輸出流;對外部定義的屬性和環境變數的訪問;載入檔案和庫的方法;還有快速複製陣列的一部分的實用方法。 System.getProperties()可以確定當前的系統屬性,返回值是一個Properties; System.load(String f

java的守護執行

執行緒分類 守護執行緒(即daemon thread),是個服務執行緒,準確地來說就是服務其他的執行緒,這是它的作用——而其他的執行緒只有一種,那就是使用者執行緒。所以java裡執行緒分2種: 使用者執行緒:比如垃圾回收執行緒,就是最典型的守護執行緒 守護執行緒:就是應用程式裡的自定義執行緒

java呼叫Oracle儲存過程時,出現異常:java.sql.SQLException: ORA-00928: 缺失 SELECT 關鍵字(已解決)

在java中呼叫Oracle儲存過程時,出現異常:java.sql.SQLException: ORA-00928: 缺失 SELECT 關鍵字 //java程式碼 @Test public void testProcedure(){

java學習(十) —— java的多執行緒概述

程序概述 程序:正在執行的程式,是系統進行資源分配和呼叫的獨立單位。 程序就是一個程式在一個數據集上的一次動態執行過程。 程序一般由程式、資料集、程序控制塊三部分組成。 每一個程序都有它自己的記憶體空間和系統資源。 我們編寫的程式用來描述程序要完成哪些功能以及如何

Java的多執行緒(執行緒間通訊)

/學習筆記/ 執行緒間通訊: 多個執行緒在處理同一資源,但是任務卻不同。 先看一個例子,採用兩個執行緒執行進行輸入和輸出任務: //資源 class Resource { String name; String sex;

Java的多執行緒程式設計---面試

什麼是多執行緒 關鍵考點: a) 執行緒的概念。 b) 多執行緒的概念。 c) 多執行緒併發的含義。 答案: 執行緒是程序中的一個執行單元,又稱為輕量級程序,它和程序一樣擁有獨立的執行控制,由作業系統負責排程。 而多執行緒是這樣的一種機制:它允許在程式中併發

java程序和執行緒以及執行緒的狀態和方法

程序是cpu資源分配的最小單位,執行緒是cpu排程的最小單位。 一個程式至少有一個程序,一個程序至少有一個執行緒.  執行緒的劃分尺度小於程序,使得多執行緒程式的併發性高。 另外,程序在執行過程中擁有獨立的記憶體單元,而多個執行緒共享記憶體,從而極大地提高了程式的執行效率。

Java的多執行緒分析

一、 引言 1、程序 通俗的來說,程序就是我們開啟電腦後使用的一個個的應用程式。比如:開啟QQ會啟動一個程序,開啟瀏覽器又會啟動另一個程序。程序和我們的程式一一對應。關於程序特點,如下: (1)、 每個程序對應一定的記憶體地址空間,並且只能使用它自己的記憶體空間,各個程序間互

Java的多執行緒(2)

概述: 上一篇文章簡單的介紹了什麼是執行緒,以及執行緒的生命週期,還有建立執行緒的三種方式。接著本篇文章將總結有關執行緒同步的相關知識,主要講解使用synchronized實現執行緒同步。然後總結Java中鎖機制,明確什麼是物件鎖,什麼是類鎖。然後下篇文章講解關於Lock的