In Java code file compilation, a public, static, and final field name class of type java.lang.Class is added as an identity in the emitted byte code to support the most fundamental phenomena in object world—class loading.
Java’s class loading architecture abstracts the virtual machine from the file source during program execution (by providing the required classes from a file system, database, or even over a network). It also prevents malicious code from interfering with benevolent code with the use of guarded namespaces. The customization in this architecture made it possible to write an extensible framework in Java.
Class loading is guided by three simple principles:
- Delegation – The delegation principle guides us when class is not already loaded. In such a case, the child class loader asks its parent to load the class until bootstrap class loader is checked (It ensures same parent class on top of object hierarchy java.lang.Object)
- Visibility – The visibility principle ensures that the child class loader can see all the classes loaded by its parent. But, the inverse of that is not true, the parent can’t see the class loaded by its child (otherwise it’s like a flat class loader, loading everything without any isolation level)
- Uniqueness – The uniqueness principle ensure that a class will be loaded exactly once in the lifetime of a class loader hierarchy (since the child has visibility to parent class, it never tries to load classes already loaded by the parent, but two siblings may end up loading the same class in their respective class loaders)
A class can be loaded as early as when its first reference is encountered in its current class or as late as a new keyword is encountered (initialization) and also when a static reference is seen using variable or method. Virtual machine does the following in order to load a class:
- Finding binary representation of a class/interface type for a given name
- Creating a class/interface from binary representation found in the above step
Every Class object contains a reference to the Class loader that brings it to life.
J2ee Containers – Master of Class Loading
They also run as a Java process. The following is a default for most of the containers, it loads all libraries (jar modules in ear) in the application root class loader and creates a new class loader for each web module as a child of an application root class loader (war module in ear). The JSP class obtains its own class loader, which is a child of web module class loader.
Alright, but if this architecture is so strict then how come some containers allow preferred loading classes from specific folders? (Like WEB-INF/lib or WEB-INF/classes)
The container writes a filtering class loader to intercept the class loading for these special classes. This intercepting class loader throws ClassNotFoundException and current class loader understands that all previous class loaders has failed to find and load the class hence it tries to load the class or otherwise throws the same exception to allow its child to load these classes.
A Note on Remote Method Invocation (RMI)
But, I still smell a problem; let us verify this understanding for RMI invocation,
- RMI uses deserialization, and deserializing an object from stream requires knowledge of application classes.
- Deserialization code is loaded by bootstrap class loader.
Aha! I see the conflict here, classes loaded by the bootstrap class loader need access for classes loaded by the application class loader.
Sun ended up solving this problem by writing a private native method in JRE. Additionally, they foresaw the problem in this model that libraries provided by a container may need knowledge of user defined classes and came up with the idea of thread context class loader. Like any other thread local variable, if initialized the thread context class loader will also be available inside the thread (along with current class loader), so library classes should consult with context class loader if their own class loader fails to find a class.
About Custom Class Loader
Class loader architecture supports customization so that we can use our own class loader if we need to devise our own mechanism of finding/loading a class during run-time. For example, we have an application that supports extending it via plug-ins. This plug-ins brings their own classes and application needs tight control over the classes loaded by plug-ins so it partition plug-in classes by loading them with different class loader. This custom class loader will need it own definition of findClass method to look classes from that plug-in directory.
There may be several security concerns when writing custom class loader, e.g. path manipulation where a class name may contain a meta path character like '../' or package name of plug-in class (they are loaded dynamically) starts with an application package name and malicious plug-in gets access to default methods of application package and so on.
Versioning and OSGi
Class loaders play a key role in the OSGi architecture. If you want to maintain different versions of a jar file for different applications (say log4j.jar 1.2.17/ 1.1.3) running in your container, you would load each jar file in a different class loader and delegate the actual loading to a specific class loader. Also, you would maintain some central registry to get a loaded class by referring to corresponding jar class loader.
This is exactly what OSGi does—the framework uses one class loader per bundle and the platform helps to resolve Import-Package/Require-Bundle reference by delegating it to a corresponding exporter bundle class loader hence maintaining different versions of jar files.
Traditionally, we had to trust a library before we ran it, but Java class loaders provide the mechanism that allows Java applications to be dynamically extended in a controlled way with additional Java code. Class loader customization in sandbox environment is one of the corner stones of Java platform security, but we have only touched the tip of the iceberg. We avoided discussion on specific methods in Class loader, like loadClass method which can cause deadlock in custom implementation or class unloading, for the sake of conciseness in this article.