This website uses cookies for visitor traffic analysis. By using the website, you agree with storing the cookies on your computer.More information

Adding new paths for native libraries at runtime in Java

If you want to add some native libraries at runtime, it was tricky with Java 8+:

This was a valid solution:

final Field fldUsrPaths = ClassLoader.class.getDeclaredField("usr_paths");
fldUsrPaths.setAccessible(true);
 
//get array of paths
final String[] saPath = (String[])fldUsrPaths.get(null);
 
//check if the path to add is already present
for (String path : saPath)
{
    if (path.equals(pPath))
    {
        return;
    }
}
 
//add the new path
final String[] saNewPaths = Arrays.copyOf(saPath, saPath.length + 1);
saNewPaths[saNewPaths.length - 1] = pPath;
fldUsrPaths.set(null, saNewPaths);

Since Java 10+ it's not possible anymore, because of: JDK-8210522
Some details from Stackoverflow

If someone still needs a solution for Java 10+, here it is:

Lookup cl = MethodHandles.privateLookupIn(ClassLoader.class, MethodHandles.lookup());
VarHandle usr_paths = cl.findStaticVarHandle(ClassLoader.class, "usr_paths", String[].class);
String[] path = (String[])usr_paths.get();

...

usr_paths.set(new String[] {"A", "B"});

This code won't work if you are using Java 8. If you want support for e.g. Java < 10, simply use reflection :)
Sounds simple, but it is not simple.....

Here's a working solution: toPDF project (search for addLibraryPath(String pPath)).

The code in detail:

Class<?> clsMHandles = Class.forName("java.lang.invoke.MethodHandles");
       
Method mStaticLookup = clsMHandles.getMethod("lookup");
Object oStaticLookup = mStaticLookup.invoke(null);

Method mLookup = clsMHandles.getMethod("privateLookupIn", Class.class, Class.forName("java.lang.invoke.MethodHandles$Lookup"));
Object oLookup = mLookup.invoke(null, ClassLoader.class, oStaticLookup);

Method mFindStatic = oLookup.getClass().getMethod("findStaticVarHandle", Class.class, String.class, Class.class);

Object oVarHandle = mFindStatic.invoke(oLookup, ClassLoader.class, "usr_paths", String[].class);

//MethodHandle mh = MethodHandles.lookup().findVirtual(VarHandle.class, "get", MethodType.methodType(Object.class));
//mh.invoke(oVarHandle);

Method mFindVirtual = oStaticLookup.getClass().getMethod("findVirtual", Class.class, String.class, Class.forName("java.lang.invoke.MethodType"));

Class<?> clsMethodType = Class.forName("java.lang.invoke.MethodType");
Method mMethodType = clsMethodType.getMethod("methodType", Class.class);

Object oMethodHandleGet = mFindVirtual.invoke(oStaticLookup, Class.forName("java.lang.invoke.VarHandle"), "get", mMethodType.invoke(null, Object.class));

Method mMethodHandleGet = oMethodHandleGet.getClass().getMethod("invokeWithArguments", Object[].class);

String[] saPath = (String[])mMethodHandleGet.invoke(oMethodHandleGet, new Object[] {new Object[] {oVarHandle}});

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

//add the new path
final String[] saNewPaths = Arrays.copyOf(saPath, saPath.length + 1);
saNewPaths[saNewPaths.length - 1] = pPath;

//MethodHandle mh = MethodHandles.lookup().findVirtual(VarHandle.class, "set", MethodType.methodType(Void.class, Object[].class));
//mh.invoke(oVarHandle, new String[] {"GEHT"});

mMethodType = clsMethodType.getMethod("methodType", Class.class, Class.class);

Object oMethodHandleSet = mFindVirtual.invoke(oStaticLookup, Class.forName("java.lang.invoke.VarHandle"), "set", mMethodType.invoke(null, Void.class, Object[].class));

Method mMethodHandleSet = oMethodHandleSet.getClass().getMethod("invokeWithArguments", Object[].class);

mMethodHandleSet.invoke(oMethodHandleSet, new Object[] {new Object[] {oVarHandle, saNewPaths}});

Not simple!!!

Summarized: It's not easy but it's still possible. So.... why are things getting more and more complex?

5 Responses to “Adding new paths for native libraries at runtime in Java”

  1. Kris Kristensen says:

    Hi,

    This is just brillant, thanks a tonn! - however there is a little curveball to this. It won't wotk with java 13 unfortunately. Check my stacktrace below.

    If you could make it java 13 compliant, that would be highly appreciated. Thanks again.

    Stacktrace from java 13 (openjdk):
    WARNING: An illegal reflective access operation has occurred
    WARNING: Illegal reflective access using Lookup on com.foo.bar.NativeLoader (file:/classes/) to class java.lang.ClassLoader
    WARNING: Please consider reporting this to the maintainers of com.foo.bar.NativeLoader
    WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
    WARNING: All illegal access operations will be denied in a future release
    Exception in thread "main" java.lang.ExceptionInInitializerError
    Caused by: java.lang.NullPointerException

    The NPE is the saPath from your example being null. I guess it would be oodd for usr_paths to be null, so I guess reflection filetered it away.

  2. Kris Kristensen says:

    With JDK 15.01 it becomes even worse. Now usr_paths are reflected as not even being part of the ClassLoader class:

    java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at com.foo.bar.(NativeLoader.java:48)
    Caused by: java.lang.NoSuchFieldException: no such field: java.lang.ClassLoader.usr_paths/[Ljava.lang.String;/getStatic
    at java.base/java.lang.invoke.MemberName.makeAccessException(MemberName.java:980)
    at java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1117)
    at java.base/java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:3424)
    at java.base/java.lang.invoke.MethodHandles$Lookup.findStaticVarHandle(MethodHandles.java:3024)
    ... 5 more
    Caused by: java.lang.NoSuchFieldError: usr_paths
    at java.base/java.lang.invoke.MethodHandleNatives.resolve(Native Method)
    at java.base/java.lang.invoke.MemberName$Factory.resolve(MemberName.java:1087)
    at java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1114)
    ... 7 more

  3. rjahn says:

    Bad news, but maybe only because the class structure has changed.... I'll check both problems tomorrow.... Hopefully there's a possibility to enable this again.

  4. Kris Kristensen says:

    any luck so far...?

  5. rjahn says:

    Not really! It works up to Java 14 in our tests.

    The problem is that they changed the internal handling of sys path... and it was their intention to prevent this "hack".
    We'll try to find a solution but right now, it won't work with Java > 15.

Leave a Reply

Spam protection by WP Captcha-Free