Sergio
Sergio

Reputation: 8672

Adding new paths for native libraries at runtime in Java

Is it possible to add a new path for native libraries at runtime ?. (Instead of starting Java with the property java.library.path), so a call to System.loadLibrary(nativeLibraryName) will include that path when trying to find nativeLibraryName. Is that possible or these paths are frozen once the JVM has started ?

Upvotes: 21

Views: 20878

Answers (3)

drekbour
drekbour

Reputation: 3081

For Java 16+ the following works but is dangerous - even for tests which is where I use it. It cannot append a newpath to java.library.path so simply replaces some existing entry. Implementations might want to do some more checking around what was selected for removal!

// requires : --add-opens java.base/jdk.internal.loader=ALL-UNNAMED
Class c = Class.forName("jdk.internal.loader.NativeLibraries$LibraryPaths");
Lookup lookup = MethodHandles.privateLookupIn(c, MethodHandles.lookup());
String[] paths = (String[]) = lookup.findStaticVarHandle(c, "USER_PATHS", String[].class).get();

paths[paths.length - 1] = newpath; // Cannot replace static final USER_PATHS so we just replaced an entry without checking!

During transition, the following does the same but also compiles with <11 (requires --add-opens java.base/java.lang.reflect=ALL-UNNAMED in 9+) so can be used, after checking the runtime version, alongside all the other solutions in this SO.

Field pathsField = Class.forName("jdk.internal.loader.NativeLibraries$LibraryPaths").getDeclaredField("USER_PATHS");
pathsField.setAccessible(true);
String[] paths = (String[])pathsField.get(null);

Upvotes: 0

Matthew Gerring
Matthew Gerring

Reputation: 109

I used this in Java 12/13 which should work for any JVM with MethodHandles:

Lookup cl = MethodHandles.privateLookupIn(ClassLoader.class, MethodHandles.lookup());
VarHandle sys_paths = cl.findStaticVarHandle(ClassLoader.class, "sys_paths", String[].class);
sys_paths.set(null);

It has the benefit of being a Java API.

It is replaces the:

    final Field sysPathsField = ClassLoader.class.getDeclaredField("sys_paths");
    sysPathsField.setAccessible(true);
    sysPathsField.set(null, null);

Upvotes: 10

ben75
ben75

Reputation: 28706

[This solution don't work with Java 10+]

It seems impossible without little hacking (i.e. accessing private fields of the ClassLoader class)

This blog provide 2 ways of doing it.

For the record, here is the short version.

Option 1: fully replace java.library.path with the new value)

public static void setLibraryPath(String path) throws Exception {
    System.setProperty("java.library.path", path);

    //set sys_paths to null so that java.library.path will be reevalueted next time it is needed
    final Field sysPathsField = ClassLoader.class.getDeclaredField("sys_paths");
    sysPathsField.setAccessible(true);
    sysPathsField.set(null, null);
}

Option 2: add a new path to the current java.library.path

/**
* Adds the specified path to the java library path
*
* @param pathToAdd the path to add
* @throws Exception
*/
public static void addLibraryPath(String pathToAdd) throws Exception{
    final Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths");
    usrPathsField.setAccessible(true);

    //get array of paths
    final String[] paths = (String[])usrPathsField.get(null);

    //check if the path to add is already present
    for(String path : paths) {
        if(path.equals(pathToAdd)) {
            return;
        }
    }

    //add the new path
    final String[] newPaths = Arrays.copyOf(paths, paths.length + 1);
    newPaths[newPaths.length-1] = pathToAdd;
    usrPathsField.set(null, newPaths);
}

Upvotes: 31

Related Questions