Adding new paths for native libraries at runtime in Java
Asked Answered
H

3

19

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 ?

Hydrothermal answered 14/3, 2013 at 12:23 Comment(1)
#2900304Divider
M
31

[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);
}
Monophthong answered 14/3, 2013 at 12:34 Comment(4)
indeed quite a hack :) thanks for posting it. It should not be that hacky, probably it is difficult to change that path for security reasons, not sure. I will avoid to do this but it is nice to know it exists.Hydrothermal
The Field class mentioned is: import java.lang.reflect.Field;Phosphorate
Java9 logs an "illegal reflective access operation".Sweetheart
Is there a better way to do this in Java-9 or Java-10? This type of reflective access results in a warning or exception in the former and is not permitted in the latter.Colloquialism
S
10

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);
Spontaneity answered 24/12, 2019 at 11:27 Comment(2)
This has also stopped working with JDK 16. I am thinking there is no more solution to this - the clean way is to use System.load.Twosided
The best way is to stick to older versions of java and do the hack... it is quite clear there are no good things in latest java except more restrictions that choke developersNuke
J
0

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);
Jemma answered 2/8, 2024 at 8:48 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.