Implementing Java Class Isolation Loading

By Xiao Hansong (Xiaokai)

During Java development, if different JAR packages depend on different versions of some common JAR packages, errors may occur during running because the loaded class is not the same as expected. How can we avoid this situation? This article analyzes the causes of JAR package conflicts and the implementation principles of class isolation and shares two methods for implementing custom class loaders.

1. What Is Class Isolation?

If you write enough Java code, this will definitely happen. The system has introduced a new middleware JAR package. Everything is normal during compiling, but an error will be reported as soon as it runs: . Then, you begin to find a solution and find a conflicting JAR package in hundreds of dependency packages. After solving the problem, you start to be frustrated with the middleware because it has so many different versions of JAR packages. You only wrote your code for five minutes but spent the whole day arranging the packages.

The situation above is common in the Java development process. The reason is simple. Different JAR packages depend on different versions of some common JAR packages, such as log components. Therefore, there is no problem when compiling, but an error is reported at runtime because the loaded class does not match the expectation. For example, A and B depend on V1 and V2 of C, respectively. Compared with V1, the log class of V2 adds the error method. Now, the project also introduces two JAR packages, A and B, as well as V0.1 and V0.2 of C. When packaging, Maven can only select one version of C, assuming V1 is selected. By default, all classes of a project are loaded with the same class loader, so no matter how many versions of C you rely on, eventually, only one version of C will be loaded into the Java Virtual Machine (JVM). When B attempts to access the log.error, it finds is no error method of the log. Then, it throws an exception: . This is a typical case of class conflict.

If the version is backward compatible, the class conflict problem can be solved easily. You only need to exclude the lower version. However, if the version is not backward compatible, you will have a dilemma.

Some people have proposed the class isolation technology to solve the class conflict problem and avoid the dilemma. The principle of class isolation is also very simple. It uses an independent class loader to load each module, so dependencies between different modules do not affect each other. As shown in the following figure, different modules are loaded with different class loaders. Why does this solve the class conflict? Here, a Java mechanism is used. Classes loaded by different class loaders are considered as two different classes in the JVM because the unique identifier of a class in the JVM is the class loader + class name. This way, we can load the classes of two different versions of C at the same time, even if their class names are the same. Note: The class loader refers to an instance of the class loader, and it is unnecessary to define two different class loaders. For example, in the figure, and can be different instances of the same class loader.

2. Implementing Class Isolation

As mentioned earlier, class isolation allows the JAR packages of different modules to be loaded by different class loaders. To achieve this, it is necessary to enable the JVM to load the classes we write and their associated classes using a custom class loader.

How can we achieve this? A very simple solution is that the JVM provides a setting interface for the global class loader to directly replace the global class loader. However, this cannot solve the problem that multiple custom class loaders exist at the same time.

The JVM provides a simple and effective way. I call it the class loading conduction rule: The JVM will select the class loader of the current class to load all of the referenced classes of the class. For example, we have defined two classes, TestA and TestB. TestA will reference TestB. As long as we use a custom class loader to load TestA, when TestA calls TestB, TestB will also be loaded by the JVM using the class loader of TestA. Then, TestA and all JAR package classes that are associated with its reference classes will be loaded by the custom class loader. This way, as long as we let the main method class of the module load using a different class loader, then each module will load using the class loader of the main method class. This allows multiple modules to use different class loaders separately. This is also the core principle of Open Service Gateway Initiative (OSGi) and SOFAArk to implement class isolation.

After understanding the implementation principle of class isolation, let’s start with rewriting the class loader. To implement our class loader, first, we should let the custom class loader inherit , and then override the class loading method. Here, we have two choices, one is to override , and the other is to override . So, which one should we choose? What is the difference between the two choices?

Next, we will try to override these two methods to implement the custom class loader.

2.1 Override

First, we define two classes. TestA will print its class loader and then call TestB to print its class loader. We expect the class loader , which overrides the method, will automatically load TestB after loading TestA.

Then, we need to override the method, which loads the class file based on the file path, and then calls to obtain the object.

Finally, you can write the main method to call the custom class loader to load TestA, and then call the main method of TestA through reflection to print the information of the class loader.

The execution result is listed below:

The execution result is not the same as expected. TestA was loaded by , but TestB was still loaded by . Why does this happen?

To answer this question, we first need to understand a class loading rule: The JVM calls the method when triggering class loading. This method implements the parent-parent delegation mechanism:

  • Delegate the parent loader to query
  • If the parent loader cannot query, we can call the method to load it.

After understanding this rule, we find the reason for the execution result: The JVM uses to load TestB, but because of the parent-parent delegation mechanism, TestB is entrusted to the , the parent loader of , for loading.

You may also wonder why the parent loader of is . The main method class we define is loaded by the that comes with Java Development Kit (JDK) by default. According to the class loading conduction rules, is referenced by the main class and loaded by the that loads the main class. Since the parent class of is ClassLoader, the default construction method of ClassLoader automatically sets the value of the parent loader to .

2.2. Override the LoadClass

Overriding the method is affected by the parent-parent delegation mechanism, causing to load TestB to be loaded, which does not meet the class isolation target. Therefore, we can only override the method to destroy the parent-parent delegation mechanism. The code is shown below:

Note: We have overridden the method, which means that all of the classes (including the classes in the ) will be loaded through . However, the target of class isolation does not include these classes that come with JDK, so we use to load JDK classes. The relevant code is: .

The test code is listed below:

The execution result is listed below:

After overriding the method, we have successfully loaded TestB into the JVM using .

3. Summary

The class isolation technology was created to solve dependency conflicts. It destroys the parent-parent delegation mechanism by customizing the class loader and then implements class isolation for different modules using the class loading conduction rules.

Reference

Explore the Java Class Loader (article in Chinese)

Original Source:

Follow me to keep abreast with the latest technology news, industry insights, and developer trends.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store