How do I get a list of JNI libraries which are loaded?

Just what the subject says, is there a way in Java to get a list of all the JNI native libraries which have been loaded at any given time?


Solution 1:

There is a way to determine all currently loaded native libraries if you meant that. Already unloaded libraries can't be determined.

Based on the work of Svetlin Nakov (Extract classes loaded in JVM to single JAR) I did a POC which gives you the names of the loaded native libraries from the application classloader and the classloader of the current class.

First the simplified version with no bu....it exception handling, nice error messages, javadoc, ....

Get the private field in which the class loader stores the already loaded libraries via reflection

public class ClassScope {
    private static final java.lang.reflect.Field LIBRARIES;
    static {
        LIBRARIES = ClassLoader.class.getDeclaredField("loadedLibraryNames");
        LIBRARIES.setAccessible(true);
    }
    public static String[] getLoadedLibraries(final ClassLoader loader) {
        final Vector<String> libraries = (Vector<String>) LIBRARIES.get(loader);
        return libraries.toArray(new String[] {});
    }
}

Call the above like this

final String[] libraries = ClassScope.getLoadedClasses(ClassLoader.getSystemClassLoader()); //MyClassName.class.getClassLoader()

And voilá libraries holds the names of the loaded native libraries.

Get the full source code from here

Solution 2:

I've built on top of jitter's solution. This allows you to find out who (ClassLoader, Class) loaded each native library.

import java.lang.reflect.Field;
import java.util.*;
import java.util.Map.Entry;

/**
 * Helper functions for native libraries.
 * <p/>
 * @author Gili Tzabari
 */
public class NativeLibraries
{
    private final Field loadedLibraryNames;
    private final Field systemNativeLibraries;
    private final Field nativeLibraries;
    private final Field nativeLibraryFromClass;
    private final Field nativeLibraryName;

    /**
     * Creates a new NativeLibraries.
     * <p/>
     * @throws NoSuchFieldException if one of ClassLoader's fields cannot be found
     */
    public NativeLibraries() throws NoSuchFieldException
    {
        this.loadedLibraryNames = ClassLoader.class.getDeclaredField("loadedLibraryNames");
        loadedLibraryNames.setAccessible(true);

        this.systemNativeLibraries = ClassLoader.class.getDeclaredField("systemNativeLibraries");
        systemNativeLibraries.setAccessible(true);

        this.nativeLibraries = ClassLoader.class.getDeclaredField("nativeLibraries");
        nativeLibraries.setAccessible(true);

        Class<?> nativeLibrary = null;
        for (Class<?> nested : ClassLoader.class.getDeclaredClasses())
        {
            if (nested.getSimpleName().equals("NativeLibrary"))
            {
                nativeLibrary = nested;
                break;
            }
        }
        this.nativeLibraryFromClass = nativeLibrary.getDeclaredField("fromClass");
        nativeLibraryFromClass.setAccessible(true);

        this.nativeLibraryName = nativeLibrary.getDeclaredField("name");
        nativeLibraryName.setAccessible(true);
    }

    /**
     * Returns the names of native libraries loaded across all class loaders.
     * <p/>
     * @return a list of native libraries loaded
     */
    public List<String> getLoadedLibraries()
    {
        try
        {
            @SuppressWarnings("UseOfObsoleteCollectionType")
            final Vector<String> result = (Vector<String>) loadedLibraryNames.get(null);
            return result;
        }
        catch (IllegalArgumentException | IllegalAccessException e)
        {
            throw new AssertionError(e);
        }
    }

    /**
     * Returns the native libraries loaded by the system class loader.
     * <p/>
     * @return a Map from the names of native libraries to the classes that loaded them
     */
    public Map<String, Class<?>> getSystemNativeLibraries()
    {
        try
        {
            Map<String, Class<?>> result = new HashMap<>();
            @SuppressWarnings("UseOfObsoleteCollectionType")
            final Vector<Object> libraries = (Vector<Object>) systemNativeLibraries.get(null);
            for (Object nativeLibrary : libraries)
            {
                String libraryName = (String) nativeLibraryName.get(nativeLibrary);
                Class<?> fromClass = (Class<?>) nativeLibraryFromClass.get(nativeLibrary);
                result.put(libraryName, fromClass);
            }
            return result;
        }
        catch (IllegalArgumentException | IllegalAccessException e)
        {
            throw new AssertionError(e);
        }
    }

    /**
     * Returns a Map from the names of native libraries to the classes that loaded them.
     * <p/>
     * @param loader the ClassLoader that loaded the libraries
     * @return an empty Map if no native libraries were loaded
     */
    public Map<String, Class<?>> getNativeLibraries(final ClassLoader loader)
    {
        try
        {
            Map<String, Class<?>> result = new HashMap<>();
            @SuppressWarnings("UseOfObsoleteCollectionType")
            final Vector<Object> libraries = (Vector<Object>) nativeLibraries.get(loader);
            for (Object nativeLibrary : libraries)
            {
                String libraryName = (String) nativeLibraryName.get(nativeLibrary);
                Class<?> fromClass = (Class<?>) nativeLibraryFromClass.get(nativeLibrary);
                result.put(libraryName, fromClass);
            }
            return result;
        }
        catch (IllegalArgumentException | IllegalAccessException e)
        {
            throw new AssertionError(e);
        }
    }

    /**
     * The same as {@link #getNativeLibraries()} except that all ancestor classloaders are processed
     * as well.
     * <p/>
     * @param loader the ClassLoader that loaded (or whose ancestors loaded) the libraries
     * @return an empty Map if no native libraries were loaded
     */
    public Map<String, Class<?>> getTransitiveNativeLibraries(final ClassLoader loader)
    {
        Map<String, Class<?>> result = new HashMap<>();
        ClassLoader parent = loader.getParent();
        while (parent != null)
        {
            result.putAll(getTransitiveNativeLibraries(parent));
            parent = parent.getParent();
        }
        result.putAll(getNativeLibraries(loader));
        return result;
    }

    /**
     * Converts a map of library names to the classes that loaded them to a map of library names to
     * the classloaders that loaded them.
     * <p/>
     * @param libraryToClass a map of library names to the classes that loaded them
     * @return a map of library names to the classloaders that loaded them
     */
    public Map<String, ClassLoader> getLibraryClassLoaders(Map<String, Class<?>> libraryToClass)
    {
        Map<String, ClassLoader> result = new HashMap<>();
        for (Entry<String, Class<?>> entry : libraryToClass.entrySet())
            result.put(entry.getKey(), entry.getValue().getClassLoader());
        return result;
    }

    /**
     * Returns a list containing the classloader and its ancestors.
     * <p/>
     * @param loader the classloader
     * @return a list containing the classloader, its parent, and so on
     */
    public static List<ClassLoader> getTransitiveClassLoaders(ClassLoader loader)
    {
        List<ClassLoader> result = new ArrayList<>();
        ClassLoader parent = loader.getParent();
        result.add(loader);
        while (parent != null)
        {
            result.add(parent);
            parent = parent.getParent();
        }
        return result;
    }
}

Solution 3:

Since Nicolas mentioned Scala, here is one way to do jitter's solution via JRuby (tested in 1.6 and 1.7):

require 'java'
import 'java.lang.ClassLoader'
f = ClassLoader.java_class.declared_field('loadedLibraryNames')
f.accessible = true
f.value(ClassLoader.system_class_loader).to_array.to_a

Solution 4:

FWIW, here's jitter's solution again, this time as a small Scala method:

def loadedLibs: Seq[String] = {
  val libs = classOf[ClassLoader].getDeclaredField("loadedLibraryNames")
  libs.setAccessible(true)
  import scala.collection.JavaConverters._
  libs.get(ClassLoader.getSystemClassLoader())
    .asInstanceOf[java.util.Vector[String]]
    .asScala
}

Solution 5:

In Clojure, copy/pastable at the REPL:

(-> (doto (.getDeclaredField ClassLoader "loadedLibraryNames")
      (.setAccessible true))
    (.get (ClassLoader/getSystemClassLoader)))