Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is custom system classloader not working?

I'm trying to override the system's class loader using the flag -Djava.system.class.loader=MyLoader. However, MyLoader is still not being used when classes are loaded.

MyLoader's code:

public class MyLoader extends ClassLoader {
    public MyLoader(ClassLoader parent) {
        super(S(parent));
    }

    private static ClassLoader S(ClassLoader cl) {
        System.out.println("---MyLoader--- inside #constructor(" + cl + ")...");
        return cl;
    }

    @Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        System.out.println("---MyLoader--- inside loadClass(" + name + ", " + resolve + ")...");
        return super.loadClass(name, resolve);
    }
}

This is the main code:

public class Main {
    public static void main(final String args[]) throws Exception {
        System.out.println("---Main--- first line");
        System.out.println("---Main--- getSystemClassLoader(): " + ClassLoader.getSystemClassLoader());
        System.out.println("---Main--- getSystemClassLoader()'s loader: " + ClassLoader.getSystemClassLoader().getClass().getClassLoader());
        Call("javax.crypto.Cipher");
    }

    public static void Call(final String class_name) throws Exception {
        System.out.println("---Main--- calling Class.forName(" + class_name + ")...");
        Class.forName(class_name);
        System.out.println("---Main--- call complete");
    }
}

This is the output using the command java -Djava.system.class.loader=MyLoader -verbose -Xshare:off Main (cf. Eclipse run config):

[Opened C:\Program Files\Java\jre7\lib\rt.jar]
[Loaded java.lang.Object from C:\Program Files\Java\jre7\lib\rt.jar]
[Loaded java.io.Serializable from C:\Program
Files\Java\jre7\lib\rt.jar]
// etc etc... omitted since it's too long
[Loaded MyLoader from file:/C:/Documents%20and%20Settings/Owner/Desktop/Programs/Eclipse%20Workspace%202/Test93/bin/]
---MyLoader--- inside #constructor(sun.misc.Launcher$AppClassLoader@158046e)...
[Loaded sun.launcher.LauncherHelper from C:\Program Files\Java\jre7\lib\rt.jar]
[Loaded java.lang.StringCoding from C:\Program Files\Java\jre7\lib\rt.jar]
[Loaded java.lang.StringCoding$StringDecoder from C:\Program Files\Java\jre7\lib\rt.jar]
---MyLoader--- inside loadClass(Main, false)...
[Loaded Main from file:/C:/Documents%20and%20Settings/Owner/Desktop/Programs/Eclipse%20Workspace%202/Test93/bin/]
[Loaded java.lang.Void from C:\Program Files\Java\jre7\lib\rt.jar]
---Main--- first line
---Main--- getSystemClassLoader(): MyLoader@8697ce
---Main--- getSystemClassLoader()'s loader: sun.misc.Launcher$AppClassLoader@158046e
---Main--- calling Class.forName(javax.crypto.Cipher)...
[Opened C:\Program Files\Java\jre7\lib\jce.jar]
[Loaded javax.crypto.Cipher from C:\Program Files\Java\jre7\lib\jce.jar]
---Main--- call complete

As can be seen, even though Main is loaded using MyLoader, javax.crypto.Cipher is not loaded using MyLoader. The output shows that MyLoader.loadClass is only called once.

Why is MyLoader.loadClass not even called when javax.crypto.Cipher is being loaded from jce.jar?

like image 206
Pacerier Avatar asked Aug 25 '14 02:08

Pacerier


People also ask

Can we create custom ClassLoader in Java?

A custom ClassLoader can maintain a separate ClassLoader for each developer's servlet. Since a class is identified in a Java virtual machine (JVM) by its full name and the ClassLoader that loaded it, classes loaded by a different ClassLoader are effectively separated.

What is system ClassLoader in Java?

The Java ClassLoader is a part of the Java Runtime Environment that dynamically loads Java classes into the Java Virtual Machine. The Java run time system does not need to know about files and file systems because of classloaders. Java classes aren't loaded into memory all at once, but when required by an application.

How does JVM ClassLoader work?

A Java Class is stored in the form of byte code in a . class file after it is compiled. The ClassLoader loads the class of the Java program into memory when it is required. The ClassLoader is hierarchical and so if there is a request to load a class, it is delegated to the parent class loader.

When would you use a custom ClassLoader?

Custom class loaders are useful in larger architectures consisting of several module/applications. Here are the advantages of the custom class loader: Provides Modular architecture Allows to define multiple class loader allowing modular architecture.


1 Answers

Your problem is that your custom class loader is being used to load Main, but its loadClass simply delegates to the parent class loader to load Main. Therefore. within Main, if you called Main.class.getClassLoader(), it would return the sun.misc.Launcher$AppClassLoader, not MyLoader.

To see what class loader will be used for Class.forName(String) calls and symbolic references from your own class, you should print getClass().getClassLoader() (or MyClass.class.getClassLoader() from a static method). The class loader that is used is the one that defined the class whose code is currently being executed. This is the rule everywhere except when using reflection (Class.forName(String, boolean, ClassLoader)).

Once a class is loaded from the parent class loader, any classes that it loads will also use that primitive class loader. So once Main is loaded from the sun.misc.Launcher$AppClassLoader class loader, all the classes that it calls will come from that same class loader, not from your own MyLoader. Similarly, once the javax.crypto.Cypher class is loaded from the null (aka Bootstrap) class loader, any classes that it mentions will also come from the bootstrap class loader except the classes it loads using reflection (SPI).

To stop loading classes from the sun.misc.Launcher$AppClassLoader class loader, set MyLoader's CLASSPATH to AppClassLoader's CLASSPATH and don't delegate classloading to AppClassLoader. Note that this will cause all the CLASSPATH classes to be loaded from MyLoader, but the classes from JDK will in general still be loaded from the null (Bootstrap) class loader.

To stop loading JDK classes from the bootstrap class loader, you must explicitly put the JDK into the classpath and modify loadClass to not check the parent first for some classes. Loading JDK classes from your own class loader is delicate; some classes (e.g. java.lang.String) must be loaded from the boostrap class loader. This is not something I have tried myself, but I have read that OSGi loads java.* from the bootstrap class loader but loads other JDK classes (e.g. sun.* and javax.*) from its own graph of class loaders.

/** Run with -Djava.system.class.loader=MyLoader to use this class loader. */
public static class MyLoader extends URLClassLoader {
    public MyLoader(ClassLoader launcherClassLoader) {
        super(getUrls(launcherClassLoader), launcherClassLoader.getParent());
    }

    private static URL[] getUrls(ClassLoader cl) {
        System.out.println("---MyLoader--- inside #constructor(" + cl + ")...");
        return ((URLClassLoader) cl).getURLs();
    }

    @Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        System.out.println("---MyLoader--- inside loadClass(" + name + ", " + resolve + ")...");
        return super.loadClass(name, resolve);
    }
}

As for the SPI factories within JDK (think XML parsers and crypto implementations), they use reflection to load named classes from either the ContextClassLoader or the SystemClassLoader or one after the other, because they want you to be able to define your own implementation, and the bootstrap class loader does not load user-defined classes. There seems to be no consistency in which one of the two they used, and I wish they just took a ClassLoader parameter instead of guessing.

like image 70
yonran Avatar answered Sep 23 '22 18:09

yonran