Imagine a bacon-wrapped Ferrari. Still not better than our free technical reports.
See all our reports

My class loader hates me and wants to slow me down

6a010535647bf3970b0192aa1c86c1970d-500wi

Disclaimer: As our readers already know, RebelLabs was set up to be a separate brand from ZeroTurnaround’s commercial side (i.e. JRebel & LiveRebel) , but over the years developers have asked us over and over again about the black magic we do, saying things like: “Yeah, it rocks, but how does JRebel actually work that class loader magic?” We’ve researched other products like Eclipse, IntelliJ, Jenkins, Maven and others, so given the deeply technical nature of this topic, and our unique proximity to Jevgeni Kabanov, the creator of JRebel and CEO of ZeroTurnaround, we decided to grill him on all of this. What follows is a look into how JRebel does what it does, from the point of view of those slothful class loaders on which the entire JVM is built. Hope you find it as interesting as we do!


Some background: can’t we all just get along?

Since Java was first created in the 1990s, Java class or resource reloading has always been a problem. Java application servers magnify this problem, adding startup and initialization times. A couple of efforts to alleviate this problem have included exploded deployment to application servers, which is really only effective with very simple applications, and Java HotSwap, created in 2001. HotSwap, when enabled, allows you to propagate your changes instantly within an existing method body. Typically, this is exposed in debug sessions but it isn’t as effective due to the method boundary limitation. Today, it’s not uncommon to have wait times or 5-15 minutes, including build, deploy and restart times. With the bigger application servers, this can easily become an order of magnitude higher.

The Problem

Once a Java class has been loaded by a class loader, it’s immutable and will last as long as the class loader itself. The identity is the class name and class loader identity, so to reload an application, you’ll actually need to create a new class loader which in turn will load the latest version of the app classes. You can’t map an existing object onto a new class, so it’s important to migrate state across around reloads. This could mean recreating the whole application object graph by reinitializing the application and config state etc or copying over user session state. Very often this is also time consuming and is very vulnerable to memory leaks.

When dealing with memory leaks with class loaders, a small one line leak can be amplified by the reference model Java uses. For instance, a class loader instance will have a reference to all classes it loads and all instances of the objects that are then created. So even a small leak, perhaps added in state migration between application instances during a reload, can in fact have much larger repercussions.

So, what does this mean for you as a developer? It means constant compilation, build, packaging, re-deployment, and application server restarts that just get in the way of your focus and your interesting productive work that you want to get on with.

This article aims to demystify the magic of JRebel to the developer, giving a glimpse of what the product does under the covers as well as giving an insight into other aspects of the JVM you may have ignored or taken for granted. This post will focus very much on the problem for which JRebel exists to solve.

Let’s look at class loaders

A class loader is just a plain java object

Yes, it’s nothing clever, well other than the system class loader in the JVM, a class loader is just a java object! It’s an abstract class, ClassLoader, which can be implemented by a class you create. Here is the API:

public abstract class ClassLoader {
  public Class loadClass(String name);
  protected Class defineClass(byte[] b);
  public URL getResource(String name);
  public Enumeration getResources(String name);
  public ClassLoader getParent();
}

Looks pretty straightforward, right? Let’s take a look method by method. The central method is loadClass which just takes a String class name and returns you the actual Class object. This is the method which if you’ve used class loaders before is probably the most familiar as it’s the most used in day to day coding. defineClass is a final method in the JVM that takes a byte array from a file or a location on the network and produces the same outcome, a Class object.

A class loader can also find resources from a classpath. It works in a similar way to the loadClass method. There are a couple of methods, getResource and getResources, which return a URL or an Enumeration of URLs which point to the resource which represents the name passed as input to the method.

Every class loader has a parent; getParent returns the class loader’s parent, which is not Java inheritance related, rather a linked list style connection. We will look into this in a little more depth later on.

Class loaders are lazy, so classes are only ever loaded when they are requested at runtime. Classes are loaded by the resource which invokes the class, so a class, at runtime, could be loaded by multiple class loaders depending on where they are referenced from and which class loader loaded the classes which referen… oops, I’ve gone cross-eyed! Let’s look at some code.

public class A {
  public void doSmth() {
    B b = new B();
    b.doSmthElse();
  }
}

Here we have class A calling the constructor of class B within the doSmth method.  Under the covers this is what is happening

  A.class.getClassLoader().loadClass(“B”);

The class loader which originally loaded class A is invoked to load the class B.

Class loaders are hierarchical, but like children, they don’t always ask their parents

Every class loader has a parent class loader. When a class loader is asked for a class, it will typically go straight to the parent class loader first calling loadClass which may in turn ask it’s parent and so on. If two class loaders with the same parent are asked to load the same class, it would only be done once, by the parent. It gets very troublesome when two class loaders load the same class separately, as this can cause problems which we’ll look at later.

When Java EE application servers implement the Java EE spec, some prefer to delegate to the parent always, but others choose to look locally within the Web App class loader first. Let’s go into this in more depth and use figure 1 as our example.

Figure 1

In this example, Module WAR1 has its own class loader and prefers to load classes itself rather than delegate to it’s parent, the class loader scoped by App1.ear. This means different WAR modules, like WAR1 and WAR2 cannot see each others classes. The App1.ear module has its own class loader and is parent to the WAR1 and WAR2 class loaders.  The App1.ear class loader is used by the WAR1 and WAR2 class loaders when they needs to delegate a request up the hierarchy i.e. a class is required outside of the WAR class loader scope. Effectively the WAR classes override the EAR classes where both exist. Finally the EAR class loader’s parent is the container class loader.  The EAR class loader will delegate requests to the container class loader, but it does not do it in the same way as the WAR class loader, as the EAR class loader will actually prefer to delegate up rather than prefer local classes. As you can see this is getting quite hairy and is different to the plain JSE class loading behaviour.

So how do we reload classes in this application?

We know from looking at the ClassLoader API earlier, that you are only able to load classes. That is to say, there’s no way to unload or reload classes, so in order to recreate a class at runtime you actually have to throw away the entire class hierarchy structure and recreate it in order to load the new class and use it at runtime, as shown in Figure 2.

reloading-object
Figure 2

If you have programmed in Java for some time you know that memory leaks do happen. Usually it’s the case of a collection somewhere with references to objects (e.g. listeners) that should have been cleared, never were. Class loaders are a very special case of this, and unfortunately, with the current state of the Java platform, these leaks are both inevitable and costly: routinely causing OutOfMemoryErrors in applications after a small number of redeploys.

Every object had a reference to its class, which in turn had a reference to its class loader. The key part though is that every class loader in turn has a reference to every class it has ever loaded, each of which holds static fields defined in the class as shown in figure 3:

classloader-refs
Figure 3

This means that:

  1. If a class loader is leaked it will hold on to all its classes and all their static fields. Static fields commonly hold caches, singleton objects, and various configuration and application states. Even if your application doesn’t have any large static caches, it doesn’t mean that the framework you use doesn’t hold them for you (e.g. Log4J is a common culprit as it’s often put in the server classpath). This explains why leaking a class loader can be so expensive.

  1. To leak a class loader it’s enough to leave a reference to any object, created from a class, loaded by that class loader. Even if that object seems completely harmless (e.g. doesn’t have a single field), it will still hold on to its class loader and all the application state. A single place in the application that survives the redeploy and doesn’t do a proper cleanup is enough to sprout the leak. In a typical application there will be several such places, some of them almost impossible to fix due to the way third-party libraries are built. Leaking a class loader is therefore, quite common.

So this is the technical problem under the covers which means in order to refresh our code at runtime, we typically need to build, package, re-deploy and even restart our containers to see our updated code. Next we’ll take a look at a couple of workarounds to this core problem in Java, including HotSwap a class reloading framework introduced in Java 1.4, and JRebel.


If you’d like to see a bit more about what JRebel is capable of, join us on these product pages and click this button here:

MORE ABOUT JREBEL