Java Classloading in Depth. Part 2. Loading mechanics
1. Introduction
In the previous part, we talked about what ClassLoader is in Java, its responsibilities, and also what types of ClassLoaders exist in Java. In this part, we’ll build a complete understanding of how the ClassLoading mechanism works in detail. I’ll mention again, that this article is targeting Java before version 9, i.e. before project jigsaw. But, it is still very important to understand how classloading worked before Java 9, to understand what changed and why. So, let’s start.
2. Loading Hierarchy
Let’s understand when JVM loads classes and how.
Firstly, JVM eagerly loads bootstrap classes (.class files in rt.jar). “Loading” itself is an overloaded word here since it implies a lot of stuff that we’ll review later, let’s leave it just “loading” for now.
As we discussed, there are 3 built-in ClassLoaders: Bootstrap, Extension, and System. They form a hierarchy:
So, every class in Java is loaded via this chain. The basic principle is: if ClassLoader has a parent ClassLoader, then it will allow the parent first to try to load this class. If the parent can load the class, then it loads it and the child does not do anything. It essentially means that every class in Java is first attempted for loading via Bootstrap Classloader.
There are multiple reasons why this is designed in this way. First, is that it helps avoid class conflict. It will ensure that java.lang.String is exactly the String class we expect, and it is not possible to create a new String class hoping that System ClassLoader will pick it up, and it would mess up the runtime. This is, again, because the System Classloader will load Class only if the parent cannot do it, same with the Extension Classloader.
The second reason is security concerns. Classes, loaded by Bootstrap Classloader, are considered internal to JDK, therefore they have some special permissions (For instance, to use a sun.misc.Unsafe). This is achieved because each JDK internal class is loaded with Bootstrap Classloader or Extension Classloader, not System Classloader. Remember, class in Java is identified by:
- Package name + Class name (or simply FQCN)
- And its ClassLoader
And in Oracle JDK for instance some security mechanisms rely on the fact that JDK classes are mostly loaded by Bootstrap Classloader, therefore, they are permitted to do something. But other classes, loaded by System ClassLoader, are not permitted. And JVM perfectly knows what class is loaded by whom.
3. Loading vs Linking
When we say “loading a class”, we need to clearly understand, what we mean by saying “loading”. From now on, “loading” means this:
Loading is the process of finding the binary representation of a class or interface type with a particular name and creating a class or interface from that binary representation.
JLS, Chapter 5
To simplify, loading is the process when JVM locates .class bytecode file, and then creates an appropriate Class class instance for this class.
But simply loading the class is not enough. There are plenty of other things that need to be done before using a loaded class — the class may have dependencies that are not loaded yet (because, remember, in JVM, loading classes other than in rt.jar is lazy), we need to also set the default values of the fields and so on.
Thus, long story short, there are a bunch of other stages that happen after the class is loaded. These stages are:
- Verification
- Preparation
- Resolution
The first two/three phases above are also commonly referred to as the linking process:
Linking a class or interface involves verifying and preparing that class or interface, its direct superclass, its direct superinterfaces, and its element type (if it is an array type), if necessary
JLS, Chapter 5.4
The reason why the Resolution is optional here is that it can be applied in a deferred/lazy manner. But most of the time, when we say linking, we mean the subsequent stages of the loading (without the initialization, which is different). Let’s review each stage one by one.
3.1. Verification
Verification is the process performed by JVM to make sure that .class byte code file is structurally correct. It means that the constant pool is correct, variables are initialized before they can be accessed, and the referenced fields exist and e.t.c.
Get Mikhail Polivakha’s stories in your inbox
Join Medium for free to get updates from this writer.
If, for any reason, the JVM has considered that .class file is not structured well, then the instance of VerifyError is thrown by the JVM in the thread that has caused the class verification.
Also, we must note, that the verification itself can trigger loading of the other classes. That is, for instance, if we are verifying class A that has imported class B, then the process of class A verification would trigger the loading of class B:
Verification may cause additional classes and interfaces to be loaded (§5.3) but need not cause them to be verified or prepared
JLS Chapter 5.4.1
3.2. Preparation
Preparation is the process that involves creating the static fields for a class or interface and initializing such fields to their default values.
It is worth mentioning that static blocks are a part of initialization of the class, not preparation:
This does not require the execution of any Java Virtual Machine code; explicit initializers for static fields are executed as part of initialization (§5.5), not preparation.
JLS Chapter 5.4.2
For instance, if we have some static fields like boolean, int, or reference types, then during preparation those fields will be created and initialized with values false, 0, and null respectively (if nothing else was specified).
3.3. Resolution
Let’s first look at the definition in the JLS, and then we’ll try to understand
Resolution is the process of dynamically determining concrete values from symbolic references in the run-time constant pool.
Not very easy to digest. To put it simply, the Resolution means checking the .class constant pool, and then making sure that these references are pointing to existing, linked classes. If classes were not linked before (which is again permitted by JLS) then referenced classes are linked in the Resolution phase. This is somehow the final linking stage. When we pass the Resolution phase, we know for sure that the class itself is ready for initialization, its bytecode is correct, and its dependencies are resolved.
5. Example
So, let’s finally try to assemble all we know for now into one particular example:
package edu.my_package.code;
import edu.mpolivaha.code.Util;
public class Main {
public static void main(String[] args) {
System.out.println("Start processing");
boolean expression = new Util().isProgrammingLanguage("Java");
System.out.println(expression);
}
}And also the Util class:
public class Util {
private static final Pattern PATTERN;
static {
PATTERN = Pattern.compile("(Java|Kotlin|Go|Rust)");
}
public boolean isProgrammingLanguage(String source) {
return PATTERN.matcher(source).matches();
}
}Now, going step-by-step:
- When JVM process is started, it asks the Bootstrap Classloader to load and link classes located in rt.jar. Once done, JVM locates the main() method, which is in my case in the Main class.
- System ClassLoader then loads the Main class. That's because Bootstrap and Extension ClassLoader do not know how to load the Main class — The Main class is not a part of rt.jar, nor it is an extension class.
- The Main class is then linked, and initialized by System Classloader.
- Since Main is using the Util class, which is not located in rt.jar, during the Main class verification phase of linking, the Util class is loaded.
- When main() method executes, it eventually reaches a new bytecode instruction to create a new instance of the Util class.
- At this point, the Util class, if not linked yet, has to be linked. Also, the Util class has to be initialized before any work with this class.
- Code in the Util class is executed and the main thread dies.
So this is how complicated it is in Java. In general, this may be hard to digest right away. It will be worth re-reading several times in your career to better understand some concepts that may seem complicated at first glance.
In the next part of this article series, we’ll discuss how ClassLoading changed in Java 9 when a new module system was introduced.










