Understanding Class Loading in Java Virtual Machine

Note: This article is written based on Java 8 (still used in many Projects) and Java 11.

We all know when we started learning OOP, Class is a blueprint of the object that needs to be constructed. Java loads its classes from the classpath defined while running the application.

Let's get started with classing loading in Java 8 and Java 11, discuss how they work behind the scene, some security concern that it exposes, and how can we handle it.

Class Loading in Java 8: 

It load classes from multiple paths, let’s see some common path,

Core classes:

  • It is loaded as a part of the Java Runtime library for example String, Thread, ArrayList, or InputStream.
  • Base classes for java are found in the java/jre/lib/rt.jar file
  • It is loaded by default when your application is running and available during runtime.

Extension classes:

  • You can find them in the extension directory (java/lib/ext/).
  • These are not necessarily shipped along with runtime classes but maybe be used as they are like runtime classes. (ex. Cryptography classes)
  • It can be used by our application.

Application classes:

  • Here classes related to our application can be referred.
  •  It can be included using `-cp ` and providing paths to the compiled class files of your application.


Delegation: It dictates how class loading works in java. There are levels of class loaders in a hierarchy fashion and they delegate the class loading to its parents. Those parents could either load classes from extension classes or core classes.

For a simple application, you will commonly find below class loaders,

  1. Bootstrap class loader
  2. Extension class loader
  3. Application class loader


If your application defines some classes which are present in core classes and having the same package structure then it will compile but during execution, it will fail. It fails because the application class loader uses delegation and due to this it will ask its parent (extension class loader) for resolving the class and the extension class loader will ask its parent. Here, Bootstrap will find the existing implementation of the class and will return that from core classes and since this class is different from what is expected. It causes runtime exceptions.

Java has the option for allowing to choose class loader path using -Xbootclasspath, -Xbootclasspath/a, -Xbootclasspath/p.

 

Class loaders don't need to delegate to its parent, and parents may load the class. Later we will see, on customizing the class loader to look for path as we want.

When class is loaded from the class loader, it is cached and the same instance is loaded next time it is asked for the same class. This behavior is found in the built-in class loader.

 

Application class loader:

  •  It will load your classes and other classes from the defined classpath while running the application.
  • When our application runs, it is invoked first and asked to resolve classes.
  • It is written in java.

 

Extension class loader:

  • It loads classes from the extension directory (extension classes).
  • It is invoked by the application class loader.
  • It is written in java

 

Bootstrap class loader:

  • It loads the classes from the runtime library.
  • It is invoked by the extension class loader.
  • It is written in C.

 

Here is the flow of class loading happening when the application asks for the class to resolve,

  1. Application will ask to Application class loader to load the class.
  2. Application class loader will delegate the request to the Extension class loader.
  3. Extension class loader will delegate the request to the Bootstrap class loader.
  4. Bootstrap class loader will check the class file in its classpath and jar.
  5. If the Bootstrap class loader cannot find the class, it will then throw the exception, which later is handled by the extension class loader.
  6. Extension class loader will check its classpath/jar/directory for the class, and if it does not find it, again exception is thrown.
  7. Application class loader, will try to look in its classpath/ directory and if it also fails to load the class, then we see the famous exception ClassNotFoundException.

During the request for class loading, each class loader will check whether it has loaded the requested class before, and if it does then the class is returned from the cache instead of delegating to its parent.

Cached classes are of type Singleton, Due to this, the same class instance is returned.

 

In Java 8, to get the reference of Application class loader, we can use the below code,

URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();


To get the reference of its parent, we can use this,

URLClassLoader parentLoader = (URLClassLoader) classLoader.getParent();


To see the classpath/directory/ jar that particular class loader uses for class resolution, we can use this,

for (URL url : classLoader.getURLs()) {

     System.out.printf("\t %s\n", url.getPath());

}


 

Class loading in Java 11

There are few changes that has been made, let’s see one by one,

Core classes:

  • Java 11 still uses Core classes.
  • Here classes in rt.jar are organized in module (since Java 9).
  • We can find this in the java/jmods directory.
  • Internal of this is not documented, but we can still list them.

 

Platform classes:

  • Here Extension classes are replaced by Platform classes.
  • If your application loads in java 11 and tries to get classes from extension classes, it will fail.

 

Delegation:

  • It is still used like in Java 8.
  • It has now, Application ClassLoader, Platform ClassLoader, and Bootstrap ClassLoader.
  • The request flow for resolving classes is the same. Here it goes from Application ClassLoader to Platform ClassLoader and then to Bootstrap ClassLoader.

 

The only thing we were able to do in Java 8 but not in Java 11 is replacing things in the core classes (like compile core classes) in your application having the same package structure but in Java 11 it doesn’t compile. Java 11 checks, core classes are not spread across packages. It is a multi-core module, so core classes should be in one module. If you try to compile it, an error will be thrown like “package exists in another module: java.base”

If we try to run the below statement in Java 11,

URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();

It will fail with ClassCastException. Previously, it was working and everyone used this to get the ClassLoader but this was the undocumented feature, and in java 11 it is removed.

Now, to get the instance of the class loader, we have to run the below statement,

ClassLoader classLoader =  ClassLoader.getSystemClassLoader();
 

To get the parent, we run this,

ClassLoader parentLoader =  classLoader.getParent();

Class Loader doesn’t have getURLs() method, so we won’t be able to check the paths used by the class loader. These class loaders have a name using, classLoader.getName().


Let’s see, how we can write our custom class Loader:

Using class Loader, we can load the class from file-based URL, network-based URL, or write our custom loader to get class from DB.

Let’s use URLClassLoader for the rest of the examples in this article.

 

Loading class from file-based URL:

To load the class from any file path, we have to construct a URL class object for the file path and pass this object array to the class loader to load from that directory instead of the default. If dependency for my application is stored in C:/Downloads/spring/demo/lib/ utils.jar, then the following will be the code to load that,

URL url = new URL("file:///c:/Downloads/spring/demo/lib/utils.jar");

URLClassLoader loader = new URLClassLoader(new URL[]{url});

Class clazz = loader.loadClass("com.pravinyo.Dummy");

Object od = clazz.getDeclaredConstructor().newInstance();

System.out.println(od.toString());


For simplicity, exception handling is not included. The problem with this approach is that Object class is used to refer to the class instance, so I won’t be able to access the method. This is the general problem with using a custom class loader. If I use com.pravinyo. Dummy, it will defeat all the purpose of using a class loader. We can split the code into implementation and interface. Here, interface definition goes with the classpath, and implementation is loaded by our class loader. Using this approach, we can use an interface in the application code to refer to the newly created instance. Here is the code sample with the interface,

public interface IDummy{

    String doSomething();

}

Package this in a separate jar(interface.jar) and let the Dummy class have a dependency on this jar. Here is the implementation of the Dummy class.

public class Dummy implements IDummy  {

    @Override

    public String doSomething() {

        return "Hello, I finished";

    }

}


Package this class in a jar (utils.jar) and this will be pulled by the Application code.

URL url = new URL("file:///c:/Downloads/spring/demo/lib/utils.jar");

URLClassLoader loader = new URLClassLoader(new URL[]{url});

Class clazz = loader.loadClass("com.pravinyo.Dummy");

IDummy od = clazz.getDeclaredConstructor().newInstance();

System.out.println(od.doSomething ());


It looks more elegant than simply using Object.


Loading class from network-based URL:

Let’s refer to the same example, the only change we need to make to allow the class loader to load from the network is passing the Web URL location instead of the file path. Here is the code,

URL url = new URL("http://localhost:8080/utils.jar");


Loading class from Database:

Currently, there is no such built-in implementation for the class loader to load classes from Database. Let’s create one small application which loads the class file from DB and executes it during runtime. Class loaded by class loader will be done in isolation with other class loaders.

If the same class file is loaded from a different instance of the same class loader, then each class loader maintains a different version of the class file.

I have created an Interface (IPerson) and package it in interface.jar and created implementation (Person) of that Interface. For our class loader to pull the class from DB, we have to store it in some table. Here, I have already created the class file and stored it as BLOB in DB.

No alt text provided for this image

Our class Loader will refer to this table If its parent class loader fails to load the requested class. Here is the code for our class loader,

public class SqlServerClassLoader extends ClassLoader {

    private final ClassLoader parent;

    private final ClassFileDataRepository repository;

 

    public SqlServerClassLoader(ClassFileDataRepository repository) {

        this(ClassLoader.getSystemClassLoader(), repository);

    }

 

    public SqlServerClassLoader(ClassLoader parent, ClassFileDataRepository repository) {

        super(parent);

        this.parent = parent;

        this.repository = repository;

    }

 

    @Override

    public Class<?> findClass(String name) throws ClassNotFoundException {

        Class<?> cls;

        try {

            cls = parent.loadClass(name);

        } catch (ClassNotFoundException cnfe) {

            try {

                cls = loadClassFromDatabase(name);

            } catch (SQLException sqle) {

                throw new ClassNotFoundException("Unable to load class", sqle);

            }

        }

        return cls;

    }

 

    private Class<?> loadClassFromDatabase(String name) throws SQLException, ClassNotFoundException {

        ClassFile returnClass;

 

        Optional<ClassFile> optionalClassFile = repository.findByName(name);

        if(optionalClassFile.isEmpty()){

            throw new ClassNotFoundException();

        }

        returnClass = optionalClassFile.get();

 

        return defineClass(name, returnClass.getClassFile(), 0, returnClass.getClassFile().length);

    }

}


Here, ‘SqlServerClassLoader’ require instance of ‘ClassFileDataRepository’. ClassFileDataRepository provides implementation to load the class details from the database. When class is requested using ‘findClass()’, It will first delegate the class loading to its parent, if the parent fails to load the class, It will try to check in the database and if found returns the class. If not found throws the Exception ‘ClassNotFoundException’. ‘defineClass()’ is used to convert the byte[] data to the class. The rest of the code is self-explanatory.

ClassFile is simply an Object Mapping for each Table row. Here is the code,

@Entity

public class ClassFile {

    @Id

    @GeneratedValue

    private int id;

 

    @Column(name = "name", columnDefinition="VARCHAR(128)")

    private String name;

 

    @Lob

    @Column(name = "class", columnDefinition="BLOB")

    private byte[] classFile;

 

    public ClassFile(){}

 

    public ClassFile(int id, String name, byte[] classFile) {

        super();

        this.id = id;

        this.name = name;

        this.classFile = classFile;

    }

 

    public ClassFile(String name, byte[] classFile) {

        super();

        this.name = name;

        this.classFile = classFile;

    }

 

    public String getName() {

        return name;

    }

 

    public void setName(String name) {

        this.name = name;

    }

 

    public byte[] getClassFile() {

        return classFile;

    }

 

    public void setClassFile(byte[] classFile) {

        this.classFile = classFile;

    }

 

    @Override

    public String toString() {

        return "ClassFile{" +

                "id=" + id +

                ", name='" + name + '\'' +

                ", classFile=" + Arrays.toString(classFile) +

                '}';

    }

}


Here is the driver code to test the class loader,

SqlServerClassLoader cl = new SqlServerClassLoader(repository);

 

Class clazz = cl.findClass("com.pravinyo.dummy.Person");

Class[] cArg = new Class[3]; //Our constructor has 3 arguments

cArg[0] = String.class; //First argument

cArg[1] = String.class; //Second argument

cArg[2] = Date.class; //Third argument

IPerson person = (IPerson) clazz.getDeclaredConstructor(cArg).newInstance("Pravin","India", new Date());

System.out.println(person);



Security issues:

   Currently, the our class loader code is not secure. Anyone can change the implementation of Person and update the class in the database. This exposes code to various kinds of vulnerability and security issues. For example, in driver code, println() calls toString() of Person object. Here, we might expect it will return some string that has details for some internal state. What if someone modified the code and changed the implementation of toString() to do some nasty things like network calls, query to database, browsing the filesystem, etc.

To address this issue, we can use the security mechanism offered by Java while running our code. Here, we can define our security policy, and grant permission based on where the code was loaded from (codebase), who created the library (signedBy), who authenticated user is(principal).

By default, the bootstrap class loader is granted all the permission. Permission for the Platform class loader is defined by the default policy of the JDK. Since Java 11 adopted the modular approach to segregate the big fatty runtime library classes, All the module’s classes are assigned a unique protection domain. The jrt URL scheme is used to refer to the class/module, and only necessary permission may be granted for them to function correctly. 

If the application is started with Security Manager, only the policy defined in JDK default policy is applied and if the application tries to access the resources that don’t has permission defined in the policy file. Access will not be provided and the Access Denied message will be returned by the security manager.

Here is the command to run the app with the security manager enabled,

// Augments (=) to default Policy:

$ java -Djava.security.manager 

-Djava.security.policy=myCustom.policy SQLClassLoader


// Override (==) the default Policy:

$ java -Djava.security.manager 

-Djava.security.policy==myCustom.policy SQLClassLoader

All the Java codes are loaded by the class loader. Code typically uses java.net.URLClassLoader which extends java.security.SecureClassLoader.

SecureClassLoader has the mapping of code source with the Permission domain, and using this allows the security manager to inspect the permission and allow or deny based on those permissions.

Let’s say we have added additional functionality where something is written to the local text file. Here is the content of the policy file (myCustom.policy) that we may use to restrict the app access using Least Privilege Permission Model,

grant codeBase "file:c:/Downloads/spring/demo/sqlclassloader/*" {

      permission java.net.SocketPermission "localhost:8080", "accept,connect,listen";

	permission java.io.FilePermission "c:/demos/message.txt", "write";

};

 

grant codeBase " file:c:/Downloads/spring/demo/lib/interface.jar" {

      permission java.io.FilePermission "c:/demos/message.txt", "write";

};


 

Alright, I get it, what is the use of this feature?

  • It is a very powerful feature that allows us to load the class from any location (local storage, Network, DB, etc.)
  • Load class from multiple locations.
  • It can also be used for Hot deployment like based on some triggers, you can automatically load a new implementation of class without any downtime to the app in production.
  • Using it with Reflection, we can achieve our own custom IoC container.
  • Using with Security Manager, we can increase the security of the app using the Least Privilege permission model.

 

Code Source: https://github.com/pravinyo/sql-class-loading-jvm

Reference:

  1.  https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html
  2. https://app.pluralsight.com/library/courses/understanding-java-vm-class-loading-reflection/

To view or add a comment, sign in

More articles by Pravin Tripathi

Others also viewed

Explore content categories