question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

ClassLoaders not controlled by update4j

See original GitHub issue

We have update4j up and running and everything works nicely beside some classloader issues. Currently these 3pps does not work without some kind of workaround:

  • Spring
  • XStream
  • Koloboke

Before loading the spring application context, which basically instantiate classes, we have to set the current classloader on the thread like this to workaround NoClassDefs:

Thread.currentThread().setContextClassLoader(tmpClassLoader);

the tmpClassLoader in the above case is taken from an already loaded class.

For XStream we have to do something similar, take the classloader from an already loaded class like this:

XStream xStream = new XStream();
xStream.setClassLoader(XStreamFactory.class.getClassLoader());

For Koloboke we haven’t figured out how to handle it.

If we look inside the koloboke code we see this:

private static class DefaultFactoryHolder {
	private static final HashIntObjMapFactory defaultFactory =
			ServiceLoader.load(HashIntObjMapFactory.class).iterator().next();
}

and when we look at JDK 11s ServiceLoader.load method, we see that the classloader is fetched like this:

@CallerSensitive
public static <S> ServiceLoader<S> load(Class<S> service) {
	ClassLoader cl = Thread.currentThread().getContextClassLoader();
	return new ServiceLoader(Reflection.getCallerClass(), service, cl);
}

Digger deeper, if the current threads context classloader is null the ServiceLoader will use the system classloader:

if (cl == null) {
	cl = ClassLoader.getSystemClassLoader();
}

All issues essentially have the same problem, getting the context classloader is either null or returns a classloader that is not controlled by update4j.

There must be a more generic way to do this. Can we configure update4j to make sure all returned classloaders are “valid” in the sense that they are controlled by update4j? Can we force each new thread to have update4js classloader when invoking:

Thread.currentThread().getContextClassLoader()

We are using update4j 1.44, AdoptOpenJDK 11.0.5 On the boot classpath we have 2 jar files: update4j and our custom boot lib, then we get the rest using the config.

If we run without update4j (using a common classpath) everythings works.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:38 (21 by maintainers)

github_iconTop GitHub Comments

3reactions
mordechaimcommented, Jan 14, 2020

I’m working on the other issues before releasing this. If you want, build from source yourself and either call:

Thread.currentThread().setContextClassLoader(new DynamicClassLoader());

in the first line of code in the bootstrap, or start the JVM with this flag:

java -Djava.system.class.loader=org.update4j.DynamicClassLoader

There is much more to this feature, which I still have to document in the wiki, but this should be a quick-fix without the need to understand much of the classloading internals.

1reaction
persalcommented, Dec 15, 2019

I got it working with below code. Let me know if you think there are cases where this won’t work. The important part is the setDelegate where it is switched out in the launcher, before starting the application. Don’t really like having to rely on reflection here, but don’t see any other solution to it.

public class ClientDelegate implements Delegate {
    static {
        DelegatingClassLoaderHolder.initDelegate();
    }
    ...
}
public class ClientUpdateHandler implements UpdateHandler {
    static {
        DelegatingClassLoaderHolder.initDelegate();
    }

    ...
}
public class ClientLauncher implements Launcher {

    ....
    
    @Override
    public void run(LaunchContext context) {
        setDelegatingClassLoader(context.getClassLoader());
        ....
    }

    private void setDelegatingClassLoader(ClassLoader classLoader) {
        try {
            // change delegate to update4j controlled classloader
            Class<?> delegatingClassLoaderholderClazz = Class.forName(CLASSLOADER_HOLDER_CLASS);
            Method delegatingMethod = delegatingClassLoaderholderClazz.getMethod(CLASSLOADER_HOLDER_METHOD, ClassLoader.class);
            delegatingMethod.invoke(delegatingClassLoaderholderClazz, classLoader);
        } catch (Exception e) {
            bootlogger.log(Level.SEVERE, e, () -> "Unable to set delegating classloader: " + e.getMessage());
        }
    }
    
    ....
}
//class available from boot classpath
public class DelegatingClassLoader extends ClassLoader {

    private Logger logger = BootLogger.getLogger(DelegatingClassLoader.class.getName());
    private volatile ClassLoader delegate;

    public void setDelegate(ClassLoader delegate) {
        logger.info(() -> "Delegate classloader " + delegate);
        this.delegate = delegate;
    }

    private ClassLoader resolveClassLoader() {
        return delegate;
    }

    @Override
    public String getName() {
        if (delegate == null) {
            return getClass().getName() + this; // must always return a name to build identity
        }
        return resolveClassLoader().getName();
    }   
    
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return resolveClassLoader().loadClass(name);
    }
	....
    //override all other public methods
}
//class available from boot classpath
public class DelegatingClassLoaderHolder {

    private static final Logger logger = BootLogger.getLogger(DelegatingClassLoaderHolder.class.getName());
    private static volatile DelegatingClassLoader delegatingClassLoader;

    public static synchronized void initDelegate() {
        if (delegatingClassLoader == null) {
            final ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
            try {
                delegatingClassLoader = new DelegatingClassLoader();
                delegatingClassLoader.setDelegate(currentContextClassLoader);
                Thread.currentThread().setContextClassLoader(delegatingClassLoader);
                initContextClassLoaderReferences();
            } finally {
                Thread.currentThread().setContextClassLoader(currentContextClassLoader);
            }
        }
    }

    /**
     * Init references to contextclassloader that is kept throughout application lifetime
     */
    private static void initContextClassLoaderReferences() {
        try {
            Toolkit.getDefaultToolkit().getSystemEventQueue();
        } catch (Exception e) {
            logger.log(Level.SEVERE, e, () -> "Unable to init EventQueue " + e.getMessage());
        }
    }

    public static void setDelegate(ClassLoader delegate) {
        if (delegatingClassLoader != null) {
            delegatingClassLoader.setDelegate(delegate);
        }
    }
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

Class loading and update detection settings - IBM
To control when synchronization occurs on multiple-server products, deselect Synchronize changes with nodes on the Console preferences page.
Read more >
update4j/update4j - Gitter
To get out of this dilema I use 3 Maven POMs.- First I analyze the maven dependencies of the bootstrap application. Than I...
Read more >
Index (update4j 1.5.9 API)
Bootstrap - Class in org.update4j ... All files were passed for an update check (or not, ... DynamicClassLoader(ClassLoader) - Constructor for class ...
Read more >
Understanding WebLogic Server Application Classloading
Java classloaders do not have any standard mechanism to undeploy or unload a set of classes, nor can they load new versions of...
Read more >
Released update4j 1.4.5; a new classloading model : r/java
That is not my landing page by the way. I am not OP and not a maintainer of that project, not even a...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found