Dynamic JRE: Stressing the machine

When considering whether to use WebLogic Server as a Grails EmbeddableServer, replacing Tomcat or Jetty in the execution of ‘grails run-app’, I came across with some limitations. Certain features of the Java runtime environment are not prepared to be changed after starting the JVM (or are they?). Despite all hype, it’s just software and use it in an unconventional manner won’t trigger the end of the world.

The following solutions (workarounds or tricks, if you prefer) require a SecurityManager or security policies permissive enough. They can be implemented using Groovy or “pure” Java. Basically makes use of AcessibleObject.setAcessible and how some classes of Java core libraries work internally, in particular the implementations available with the Oracle/Sun JDK or JRE and the OpenJDK.

1. Dynamically adding paths to the CLASSPATH (java.class.path)

change_classpath.groovy

// groovy change_classpath.groovy

def changeClasspath = {paths ->
    def systemClassLoader = ClassLoader.systemClassLoader
    assert systemClassLoader.metaClass.respondsTo(systemClassLoader, 'addURL', java.net.URL)
    def pathSeparator = File.pathSeparator
    def classpathExtension = new StringBuilder()
    for (path in paths) {
        def lib = new File(path); assert lib.exists()
        systemClassLoader.addURL(lib.toURI().toURL())
        classpathExtension.append(pathSeparator); classpathExtension.append(lib.canonicalPath)
    }
    // Required under certain conditions
    System.setProperty('java.class.path', System.getProperty('java.class.path', '') + classpathExtension.toString())    
}

def classes = ['Lib1', 'Lib2', 'Lib3', 'Lib4']
for (className in classes) {
    try {Class.forName(className); assert false} catch(ClassNotFoundException e) {assert true}
}

def extraPaths = ['lib1.jar', 'lib2classes']
extraPaths.addAll((new File('extraLibs').list() as List).collect {'extraLibs/' + it})
changeClasspath(extraPaths)

for (className in classes) {
    def lib = Class.forName(className)
    lib.doSomething()
    println "${className}.classLoader ${lib.classLoader}"
}

ChangeClasspath.java

// java -ea ChangeClasspath

import java.lang.reflect.Method;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class ChangeClasspath {

    public static void main(String [] args) throws java.net.MalformedURLException, NoSuchMethodException, IllegalAccessException, java.lang.reflect.InvocationTargetException, ClassNotFoundException {
        String [] classes = {"Lib1", "Lib2", "Lib3", "Lib4"};
        for (String className : classes) {
            try {Class.forName(className); assert false;} catch(ClassNotFoundException e) {assert true;}
        }

        List <String> extraPaths = new ArrayList();
        extraPaths.add("lib1.jar"); extraPaths.add("lib2classes");
        for (String path : new File("extraLibs").list()) {
            extraPaths.add("extraLibs/" + path);
        }
        
        changeClasspath(extraPaths);

        for (String className : classes) {
            Class lib = Class.forName(className);
            Method doSomethingMethod = lib.getDeclaredMethod("doSomething");
            doSomethingMethod.invoke(null);
            System.out.println(className + ".classLoader " + lib.getClassLoader());
        }
    }

    private static void changeClasspath(List <String> paths) throws NoSuchMethodException, IllegalAccessException, java.net.MalformedURLException, java.lang.reflect.InvocationTargetException{
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        assert systemClassLoader instanceof java.net.URLClassLoader;

        String pathSeparator = File.pathSeparator;
        
        StringBuilder classpathExtension = new StringBuilder();
        Method addURLMethod = java.net.URLClassLoader.class.getDeclaredMethod("addURL", java.net.URL.class);
        addURLMethod.setAccessible(true);

        for (String path : paths) {
            File lib = new File(path); assert lib.exists();
            addURLMethod.invoke(systemClassLoader, lib.toURI().toURL());
        }
        // Required under certain conditions
        System.setProperty("java.class.path", System.getProperty("java.class.path", "") + classpathExtension.toString());
    }

}


2. Dynamically adding paths to java.library.path

change_librarypath.groovy

// groovy change_librarypath.groovy

def changeLibraryPath = {paths ->
    def newLibraryPath = new StringBuilder()
    for (newPath in paths) {
        def dir = new File(newPath); assert dir.exists()
        newLibraryPath.append(File.pathSeparator); newLibraryPath.append(dir.canonicalPath)        
    }
    
    System.setProperty('java.library.path', System.getProperty('java.library.path', '') + newLibraryPath.toString())
    ClassLoader.@sys_paths = null
}

def osName = System.properties['os.name'].toLowerCase()
def osArch = System.properties['os.arch'].toLowerCase()

def path = new StringBuilder('native/')
for (os in ['windows', 'linux']) {
    if (osName.contains(os)) {
        path.append(os)
        break
    }
}
path.append(osArch.contains('64')? '/x86_64': '/i686')

try {System.loadLibrary('rxtxSerial'); assert false} catch (UnsatisfiedLinkError e) {assert true}

changeLibraryPath([path.toString()])

//println System.getProperty('java.library.path')
System.loadLibrary('rxtxSerial')

3. Changing the system class loader (java.system.class.loader) of a running JVM

change_sysclassloader.groovy

// groovy change_sysclassloader.groovy

def changeSystemClassLoader = {
    System.setProperty('java.system.class.loader', 'SpecialClassLoader')
    ClassLoader.@sclSet = false
    ClassLoader.@scl = null
}

def systemClassLoader = ClassLoader.systemClassLoader
systemClassLoader.addURL(new File('.').toURI().toURL())
def specialClassLoaderClass = systemClassLoader.loadClass('SpecialClassLoader')

assert !(specialClassLoaderClass.isAssignableFrom(systemClassLoader.class))

def groovyClassLoader = new GroovyClassLoader(this.class.classLoader)
groovyClassLoader.addURL(new File('scripts').toURI().toURL())

System.properties.put('SpecialClassLoader.systemClassLoader', systemClassLoader)
System.properties.put('SpecialClassLoader.parentClassLoader', groovyClassLoader)
//specialClassLoaderClass.setClassLoaders(systemClassLoader, groovyClassLoader)

changeSystemClassLoader()

systemClassLoader = ClassLoader.systemClassLoader
assert specialClassLoaderClass.isAssignableFrom(systemClassLoader.class)

def script = systemClassLoader.loadClass('Lib')
script.test()

Download the source code.

Additional References

Leave a Reply

Your email address will not be published. Required fields are marked *

*


4 − = three

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>