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

System.loadLibrary()是咱們在使用Java的JNI機制時,會用到的一個很是重要的函數,它的做用便是把實現了咱們在Java code中聲明的native方法的那個libraryload進來,或者load其餘什麼動態鏈接庫。java

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

/**
 * 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這個文件中):linux

/*
 * 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的過程,又實際作了什麼事情?下面會對這兩個問題,一一的做出解答。android

系統的library path

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

/**
 * 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這些內容出來,就像下面這樣:ide

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這個文件中)函數

/**
 * 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的Stringthis

ClassLoader classLoader = getClassLoader();
Log.v(TAG, "classLoader = "+ classLoader.toString());
在Galaxy Nexus上執行的結果以下:

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這個文件中 ):

@Override
public String findLibrary(String name) {
    return pathList.findLibrary(name);
}

這個方法看上去倒挺簡單,不用多作解釋。而後來看那個 pathList 的初始化的過程,在 BaseDexClassLoader的構造函數裏
/**
 * 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的構造函數:spa

/**
 * 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()的實現:debug

/**
 * 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()的實現:

/**
 * 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這個文件中):

/*
 * 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中,以下:

/*
 * Load native code from the specified absolute pathname.  Per the spec,
 * if we've already loaded a library with the specified pathname, we
 * return without doing anything.
 *
 * TODO? for better results we should absolutify the pathname.  For fully
 * correct results we should stat to get the inode and compare that.  The
 * existing implementation is fine so long as everybody is using
 * System.loadLibrary.
 *
 * The library will be associated with the specified class loader.  The JNI
 * spec says we can't load the same library into more than one class loader.
 *
 * Returns "true" on success. On failure, sets *detail to a
 * human-readable description of the error or NULL if no detail is
 * available; ownership of the string is transferred to the caller.
 */
bool dvmLoadNativeCode(const char* pathName, Object* classLoader,
        char** detail)
{
    SharedLib* pEntry;
    void* handle;
    bool verbose;

    /* reduce noise by not chattering about system libraries */
    verbose = !!strncmp(pathName, "/system", sizeof("/system")-1);
    verbose = verbose && !!strncmp(pathName, "/vendor", sizeof("/vendor")-1);

    if (verbose)
        ALOGD("Trying to load lib %s %p", pathName, classLoader);

    *detail = NULL;

    /*
     * See if we've already loaded it.  If we have, and the class loader
     * matches, return successfully without doing anything.
     */
    pEntry = findSharedLibEntry(pathName);
    if (pEntry != NULL) {
        if (pEntry->classLoader != classLoader) {
            ALOGW("Shared lib '%s' already opened by CL %p; can't open in %p",
                pathName, pEntry->classLoader, classLoader);
            return false;
        }
        if (verbose) {
            ALOGD("Shared lib '%s' already loaded in same CL %p",
                pathName, classLoader);
        }
        if (!checkOnLoadResult(pEntry))
            return false;
        return true;
    }

    /*
     * Open the shared library.  Because we're using a full path, the system
     * doesn't have to search through LD_LIBRARY_PATH.  (It may do so to
     * resolve this library's dependencies though.)
     *
     * Failures here are expected when java.library.path has several entries
     * and we have to hunt for the lib.
     *
     * The current version of the dynamic linker prints detailed information
     * about dlopen() failures.  Some things to check if the message is
     * cryptic:
     *   - make sure the library exists on the device
     *   - verify that the right path is being opened (the debug log message
     *     above can help with that)
     *   - check to see if the library is valid (e.g. not zero bytes long)
     *   - check config/prelink-linux-arm.map to ensure that the library
     *     is listed and is not being overrun by the previous entry (if
     *     loading suddenly stops working on a prelinked library, this is
     *     a good one to check)
     *   - write a trivial app that calls sleep() then dlopen(), attach
     *     to it with "strace -p <pid>" while it sleeps, and watch for
     *     attempts to open nonexistent dependent shared libs
     *
     * This can execute slowly for a large library on a busy system, so we
     * want to switch from RUNNING to VMWAIT while it executes.  This allows
     * the GC to ignore us.
     */
    Thread* self = dvmThreadSelf();
    ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT);
    handle = dlopen(pathName, RTLD_LAZY);
    dvmChangeStatus(self, oldStatus);

    if (handle == NULL) {
        *detail = strdup(dlerror());
        ALOGE("dlopen(\"%s\") failed: %s", pathName, *detail);
        return false;
    }

    /* create a new entry */
    SharedLib* pNewEntry;
    pNewEntry = (SharedLib*) calloc(1, sizeof(SharedLib));
    pNewEntry->pathName = strdup(pathName);
    pNewEntry->handle = handle;
    pNewEntry->classLoader = classLoader;
    dvmInitMutex(&pNewEntry->onLoadLock);
    pthread_cond_init(&pNewEntry->onLoadCond, NULL);
    pNewEntry->onLoadThreadId = self->threadId;

    /* try to add it to the list */
    SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);

    if (pNewEntry != pActualEntry) {
        ALOGI("WOW: we lost a race to add a shared lib (%s CL=%p)",
            pathName, classLoader);
        freeSharedLibEntry(pNewEntry);
        return checkOnLoadResult(pActualEntry);
    } else {
        if (verbose)
            ALOGD("Added shared lib %s %p", pathName, classLoader);

        bool result = true;
        void* vonLoad;
        int version;

        vonLoad = dlsym(handle, "JNI_OnLoad");
        if (vonLoad == NULL) {
            ALOGD("No JNI_OnLoad found in %s %p, skipping init",
                pathName, classLoader);
        } else {
            /*
             * Call JNI_OnLoad.  We have to override the current class
             * loader, which will always be "null" since the stuff at the
             * top of the stack is around Runtime.loadLibrary().  (See
             * the comments in the JNI FindClass function.)
             */
            OnLoadFunc func = (OnLoadFunc)vonLoad;
            Object* prevOverride = self->classLoaderOverride;

            self->classLoaderOverride = classLoader;
            oldStatus = dvmChangeStatus(self, THREAD_NATIVE);
            if (gDvm.verboseJni) {
                ALOGI("[Calling JNI_OnLoad for \"%s\"]", pathName);
            }
            version = (*func)(gDvmJni.jniVm, NULL);
            dvmChangeStatus(self, oldStatus);
            self->classLoaderOverride = prevOverride;

            if (version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 &&
                version != JNI_VERSION_1_6)
            {
                ALOGW("JNI_OnLoad returned bad version (%d) in %s %p",
                    version, pathName, classLoader);
                /*
                 * It's unwise to call dlclose() here, but we can mark it
                 * as bad and ensure that future load attempts will fail.
                 *
                 * We don't know how far JNI_OnLoad got, so there could
                 * be some partially-initialized stuff accessible through
                 * newly-registered native method calls.  We could try to
                 * unregister them, but that doesn't seem worthwhile.
                 */
                result = false;
            } else {
                if (gDvm.verboseJni) {
                    ALOGI("[Returned from JNI_OnLoad for \"%s\"]", pathName);
                }
            }
        }

        if (result)
            pNewEntry->onLoadResult = kOnLoadOkay;
        else
            pNewEntry->onLoadResult = kOnLoadFailed;

        pNewEntry->onLoadThreadId = 0;

        /*
         * Broadcast a wakeup to anybody sleeping on the condition variable.
         */
        dvmLockMutex(&pNewEntry->onLoadLock);
        pthread_cond_broadcast(&pNewEntry->onLoadCond);
        dvmUnlockMutex(&pNewEntry->onLoadLock);
        return result;
    }
}


哇塞,dvmLoadNativeCode()這個函數還真的是有點複雜,那就挑那些跟咱們的JNI比較緊密相關的邏輯來看吧。能夠認爲這個函數作了下面的這樣一些事情:

  1. 調用dlopen()打開一個so文件,建立一個handle。
  2. 調用dlsym()函數,查找到so文件中的JNI_OnLoad()這個函數的函數指針。
  3. 執行上一步找到的那個JNI_OnLoad()函數。

至此,大致能夠結束System.loadLibrary()的執行過程的分析。

相關文章
相關標籤/搜索