In this post, we will be looking at a very important part of the
Java Runtime Environment (or JRE) - Class loaders.
We will look at what they are, how they work and how we can implement our own custom class loader.
What is a class loader?
Class loaders in Java are objects that have the responsibility of locating and loading the class definitions that are needed by the application.
They take a fully classified class name as a string input, meaning a class name with the full package path.
Some examples of fully classified names are:
From this class name input, they try to find or generate the data that represents the definition of the class.
That is why every class that was loaded in the application has a reference to the class loader that loaded it and we can get that information by invoking the method getClassLoader() on the class object.
How class loaders work
The entry point for loading classes is the loadClass(String name) method.
Every class loader does the following steps:
- Check if the class has already been loaded
- If the class has been loaded, return it here
- Delegate the loading to the parent classloader
- If parent returns a class, return it
- Try to load the class by calling the #findClass method
- If the class was found return it, otherwise throw a ClassNotFoundException
So we can see that all the class loaders follow the delegation model, which means they first delegate the loading of the class to the parent class loader
and only if that did not find the class do they try to load it themselves.
By following this model the class loading API guarantees that the classes are unique and that they are not loaded multiple times.
Types of ClassLoaders
There are three types of classloaders when the JVM starts up:
Bootstrap class loader
The bootstrap or primordial class loader is executed by the JVM to load all the core Java libraries that are in the $JAVA_HOME/jre/lib folder.
This class loader is written in native code and because of that has no Java class representation. That is why all the core Java library classes that are loaded by this classloader have their classloader set to null.
For example, all the classes in the packages java.util (ArrayList, HashMap, etc) and java.lang (Boolean, Byte, Char, etc…) return null if you invoke their getClassLoader() method.
System.out.println(ArrayList.class.getClassLoader()); // prints null System.out.println(HashMap.class.getClassLoader()); // prints null System.out.println(HashSet.class.getClassLoader()); // prints null System.out.println(Boolean.class.getClassLoader()); // prints null System.out.println(Byte.class.getClassLoader()); // prints null System.out.println(Character.class.getClassLoader()); // prints null
Extension class loader
The extension class loader is used to load extension classes that are located in the ext folder of the JRE (so $JAVA_HOME/jre/lib/ext)
or other folders specified by the system property java.ext.dirs.
For example, on my current machine, there are jars access-bridge-64.jar, jfxrt.jar and nashorn.jar among others in the lib/ext folder, and all the classes from those jars are loaded using the extension class loader, as we can see by calling the getClassLoader() method on some of the classes that are located in these jars.
System.out.println(AccessBridge.class.getClassLoader()); // prints sun.misc.Launcher$ExtClassLoader@45ee12a7 System.out.println(DynamicLinker.class.getClassLoader()); // prints sun.misc.Launcher$ExtClassLoader@45ee12a7 System.out.println(Logging.class.getClassLoader()); // prints sun.misc.Launcher$ExtClassLoader@45ee12a7
System or application class loader
The application class loader loads all the classes that are on the classpath of the application.
The classpath can either come from the java.class.path environment variable
or it can be passed directly to the application via -classpath or -cp arguments.
Therefore all the classes that are loaded from the classpath are loaded with the application class loader, for example:
System.out.println(MyAppClass1.class.getClassLoader()); // prints sun.misc.Launcher$AppClassLoader@18b4aac2 System.out.println(MyAppClass2.class.getClassLoader()); // prints sun.misc.Launcher$AppClassLoader@18b4aac2 System.out.println(MyAppClass3.class.getClassLoader()); // prints sun.misc.Launcher$AppClassLoader@18b4aac2
Extending the ClassLoader
The described three class loaders are usually enough to cover most use cases, where you have all the external jars that you will use locally.
But in case you do not have all the files in the file system, or you will need to load some JAR dynamically from alternate sources when the app is running there are also other options available for you to do that.
One of the most common examples is when you don’t have the external JAR files locally, but need to load them over the network. This was done a lot when applets were around so Java already has a nice prepared solution for this problem - the URLClassLoader class.
The URLClassLoader takes an array of URLs from where to load jars, so it is perfect for loading classes over the network.
The advantage of using the URLClassLoader is that you do not have to implement all the logic that is required when loading classes. You just hand the classloader the URLs where the JAR files are located, and it takes care of correctly loading the classes from there.
If your use case is different and the URLClassLoader does not provide the right functionality, you can also extend the class loader.
To create your own custom class loader, you need to extend the ClassLoader class and implement the findClass method with your custom logic for loading classes.
Example custom class loader
In our example, we will write and use a class loader that will load classes from a database.
The example contains:
- A HSQLDB file based instance
- A simple source and class file that is not on the final application classpath, which our custom class loader will load
- A custom class loader that uses the HSQLDB instance to try and load the classes
Our example class that will supply a message that we will print out is pretty simple:
From this source file, we compiled the .class file which the application loads into the database. Both are included in the classes folder in the project.
Next, we have our link to the database - the ClassDAO class. It has three methods
- createClassTable() - creates the table in the database that has two columns, className and data
- insertClass(String, byte) - inserts the passed byte array data in the database under the key string that is passed in
- getClass(String) - method to retrieve the data associated with the passed in key string
And finally our custom class loader. The class loader itself is pretty straight forward, we extend the ClassLoader class and override the findClass function, where we implement our custom class loading logic. We simply call the getClass() method on the ClassDAO, and if we find anything we define the class with the data, otherwise we throw a ClassNotFoundException.
If we run the main method in the Main class, we get the following messages printed out in the console:
From this, we can see that we successfully loaded the class from the DB using our custom class loader, while the default application class loader was unable to load the class since it is not on the application classpath.
You can check out the full project on GitHub.
This is it for our classloader post, I hope you found it informative.
Improvements to do for practice:
Things that could be improved and/or extended for practice:
- Split the application into a provider and client applications, where the provider inserts the classes into the database and the client loads them so you can load the classes into the DB asynchronously from the client