虚拟机加载so时间

chenk818 2012-03-26
java version "1.6.0_29"

java -Xbootclasspath/a:test.jar   -jar XXX.jar

其中,test.jar中用到了jni,需要加载test.so,我把java.library.path输出后,有一系列的目录,
...
/home/chenkui/allinstance/instance1/jdk/jre/lib/amd64
. (当前目录)
....
/lib64
...
其中,只有将test.so放到jdk/jre/lib/amd64下,能成功加载,放到当前目录却始终加载不上。我的理解是,虚拟机在使用so文件之前,必然已经将java.library.path下的so全加载完了,不应该跟目录的位置还有关系,求解答。。。
RednaxelaFX 2012-03-27
你的疑问大概来自这里?

    /* sysclasspath, java_home, dll_dir */
    {
        char *home_path;
        char *dll_path;
        char *pslash;
        char buf[MAXPATHLEN];
        os::jvm_path(buf, sizeof(buf));

        // Found the full path to libjvm.so.
        // Now cut the path to <java_home>/jre if we can.
        *(strrchr(buf, '/')) = '\0';  /* get rid of /libjvm.so */
        pslash = strrchr(buf, '/');
        if (pslash != NULL)
            *pslash = '\0';           /* get rid of /{client|server|hotspot} */
        dll_path = malloc(strlen(buf) + 1);
        if (dll_path == NULL)
            return;
        strcpy(dll_path, buf);
        Arguments::set_dll_dir(dll_path);

        if (pslash != NULL) {
            pslash = strrchr(buf, '/');
            if (pslash != NULL) {
                *pslash = '\0';       /* get rid of /<arch> */
                pslash = strrchr(buf, '/');
                if (pslash != NULL)
                    *pslash = '\0';   /* get rid of /lib */
            }
        }

        home_path = malloc(strlen(buf) + 1);
        if (home_path == NULL)
            return;
        strcpy(home_path, buf);
        Arguments::set_java_home(home_path);

        if (!set_boot_path('/', ':'))
            return;
    }

#define DEFAULT_LIBPATH "/usr/lib64:/lib64:/lib:/usr/lib"

    /*
     * Where to look for native libraries
     *
     * Note: Due to a legacy implementation, most of the library path
     * is set in the launcher.  This was to accomodate linking restrictions
     * on legacy Linux implementations (which are no longer supported).
     * Eventually, all the library path setting will be done here.
     *
     * However, to prevent the proliferation of improperly built native
     * libraries, the new path component /usr/java/packages is added here.
     * Eventually, all the library path setting will be done here.
     */
    {
        char *ld_library_path;

        /*
         * Construct the invariant part of ld_library_path. Note that the
         * space for the colon and the trailing null are provided by the
         * nulls included by the sizeof operator (so actually we allocate
         * a byte more than necessary).
         */
        ld_library_path = (char *) malloc(sizeof(REG_DIR) + sizeof("/lib/") +
            strlen(cpu_arch) + sizeof(DEFAULT_LIBPATH));
        sprintf(ld_library_path, REG_DIR "/lib/%s:" DEFAULT_LIBPATH, cpu_arch);

        /*
         * Get the user setting of LD_LIBRARY_PATH, and prepended it.  It
         * should always exist (until the legacy problem cited above is
         * addressed).
         */
        char *v = getenv("LD_LIBRARY_PATH");
        if (v != NULL) {
            char *t = ld_library_path;
            /* That's +1 for the colon and +1 for the trailing '\0' */
            ld_library_path = (char *) malloc(strlen(v) + 1 + strlen(t) + 1);
            sprintf(ld_library_path, "%s:%s", v, t);
        }
        Arguments::set_library_path(ld_library_path);
    }


你的程序有没有显式设置过-Djava.library.path?或者LD_LIBRARY_PATH?
默认构造出来的路径,在Linux/AMD64版的Oracle JDK里就是上面代码那样的。最终拼接出来的ld_library_path就会作为后面系统属性里的java.library.path的值;除非你覆盖过这个值(显式设置了-Djava.library.path)。

同样,上面的dll_path最终会转变为sun.boot.library.path,除非通过-D显式覆盖掉这个值。

然后你的test.jar里是如何加载test.so的?是System.loadLibrary("test");么?那个的内部实现是这样的:
    // Invoked in the java.lang.Runtime class to implement load and loadLibrary.
    static void loadLibrary(Class fromClass, String name,
                            boolean isAbsolute) {
        try {
            if (!DownloadManager.isJREComplete() && 
                    !DownloadManager.isCurrentThreadDownloading()) {
                DownloadManager.downloadFile("bin/" + 
                    System.mapLibraryName(name));
                // it doesn't matter if the downloadFile call returns false --
                // it probably just means that this is a user library, as 
                // opposed to a JRE library
            }
        } catch (IOException e) {
            throw new UnsatisfiedLinkError("Error downloading library " + 
                                                name + ": " + e);
        } catch (NoClassDefFoundError e) {
            // This happens while Java itself is being compiled; DownloadManager
            // isn't accessible when this code is first invoked.  It isn't an
            // issue, as if we can't find DownloadManager, we can safely assume
            // that additional code is not available for download.
        }
        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");
    }

    private static boolean loadLibrary0(Class fromClass, final File file) {
        Boolean exists = (Boolean)
            AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                    return new Boolean(file.exists());
                }
            });
        if (!exists.booleanValue()) {
            return false;
        }
        String name;
        try {
            name = file.getCanonicalPath();
        } catch (IOException e) {
            return false;
        }
        ClassLoader loader =
            (fromClass == null) ? null : fromClass.getClassLoader();
        Vector libs =
            loader != null ? loader.nativeLibraries : systemNativeLibraries;
        synchronized (libs) {
            int size = libs.size();
            for (int i = 0; i < size; i++) {
                NativeLibrary lib = (NativeLibrary)libs.elementAt(i);
                if (name.equals(lib.name)) {
                    return true;
                }
            }

            synchronized (loadedLibraryNames) {
                if (loadedLibraryNames.contains(name)) {
                    throw new UnsatisfiedLinkError
                        ("Native Library " +
                         name +
                         " already loaded in another classloader");
                }
                /* If the library is being loaded (must be by the same thread,
                 * because Runtime.load and Runtime.loadLibrary are
                 * synchronous). The reason is can occur is that the JNI_OnLoad
                 * function can cause another loadLibrary invocation.
                 *
                 * Thus we can use a static stack to hold the list of libraries
                 * we are loading.
                 *
                 * If there is a pending load operation for the library, we
                 * immediately return success; otherwise, we raise
                 * UnsatisfiedLinkError.
                 */
                int n = nativeLibraryContext.size();
                for (int i = 0; i < n; i++) {
                    NativeLibrary lib = (NativeLibrary)
                        nativeLibraryContext.elementAt(i);
                    if (name.equals(lib.name)) {
                        if (loader == lib.fromClass.getClassLoader()) {
                            return true;
                        } else {
                            throw new UnsatisfiedLinkError
                                ("Native Library " +
                                 name +
                                 " is being loaded in another classloader");
                        }
                    }
                }
                NativeLibrary lib = new NativeLibrary(fromClass, name);
                nativeLibraryContext.push(lib);
                try {
                    lib.load(name);
                } finally {
                    nativeLibraryContext.pop();
                }
                if (lib.handle != 0) {
                    loadedLibraryNames.addElement(name);
                    libs.addElement(lib);
                    return true;
                }
                return false;
            }
        }
    }

#define JNI_LIB_PREFIX "lib"
#define JNI_LIB_SUFFIX ".so"

static void cpchars(jchar *dst, char *src, int n)
{
    int i;
    for (i = 0; i < n; i++) {
        dst[i] = src[i];
    }
}

JNIEXPORT jstring JNICALL
Java_java_lang_System_mapLibraryName(JNIEnv *env, jclass ign, jstring libname)
{
    int len;
    int prefix_len = (int) strlen(JNI_LIB_PREFIX);
    int suffix_len = (int) strlen(JNI_LIB_SUFFIX);

    jchar chars[256];
    if (libname == NULL) {
        JNU_ThrowNullPointerException(env, 0);
        return NULL;
    }
    len = (*env)->GetStringLength(env, libname);
    if (len > 240) {
        JNU_ThrowIllegalArgumentException(env, "name too long");
	return NULL;
    }
    cpchars(chars, JNI_LIB_PREFIX, prefix_len);
    (*env)->GetStringRegion(env, libname, 0, len, chars + prefix_len);
    len += prefix_len;
    cpchars(chars + len, JNI_LIB_SUFFIX, suffix_len);
    len += suffix_len;

    return (*env)->NewString(env, chars, len);
}


于是你有没有试过把test.so改名为libtest.so?
chenk818 2012-03-27
不好意思没写清楚,我的so文件命名是有加lib的。我先照你给的代码调试一下,thx
chenk818 2012-03-27
if (sys_paths == null) { 
        usr_paths = initializePath("java.library.path"); 
        sys_paths = initializePath("sun.boot.library.path"); 
    } 

我试了下,放到usr_paths就是不行,必须得放到sys_paths下面。是不是跟我的jar包是在-Xbootclasspath/a:test.jar有关系呢?
RednaxelaFX 2012-03-27
你说得没错。抱歉,我没仔细看这点。
由于你是把JAR包加在bootclasspath上的,这JAR包里的类的classloader会是bootstrap class loader,在Java一侧表现为loader是null。

于是上面的Java代码里if (loader != null)的分支都进不去,而唯一能执行的地方就是:
for (int i = 0 ; i < sys_paths.length ; i++) {  
        File libfile = new File(sys_paths[i], System.mapLibraryName(name));  
        if (loadLibrary0(fromClass, libfile)) {  
            return;  
        }  
    } 

这段。
chenk818 2012-03-27
那就引出另外一个问题:jvm是否允许用户自定义sys_paths,用户能否将自己的目录加到sys_paths里面呢?
RednaxelaFX 2012-03-27
chenk818 写道
那就引出另外一个问题:jvm是否允许用户自定义sys_paths,用户能否将自己的目录加到sys_paths里面呢?

答案是可以的。参考例子:https://gist.github.com/2214201
显式设置-Dsun.boot.library.path会在默认的路径后拼接自己设置的路径,而不会覆盖。

  if (strcmp(key, "sun.boot.library.path") == 0) {
    PropertyList_unique_add(&_system_properties, key, value, true);
    return true;
  }

VM处理参数的逻辑会对这个参数做特殊处理,使它采用append而不是overwrite的方式设置属性。

喵的,这些处理方式的坑真多…
Global site tag (gtag.js) - Google Analytics