๐Ÿš€ OharaLumina

Unloading classes in java

Unloading classes in java

๐Ÿ“… | ๐Ÿ“‚ Category: Java

Delving into the intricacies of Java’s runtime environment often brings developers to the nuanced topic of unloading classes in Java. While objects are routinely garbage collected once they are no longer referenced, the lifecycle of a loaded class is considerably more complex. Understanding how and when Java classes are unloaded is crucial for robust memory management, especially in long-running applications, dynamic environments like application servers, or systems with plugin architectures. This often-overlooked aspect directly impacts application stability and performance, preventing insidious memory leaks that can slowly degrade system resources over time. Effectively managing class lifecycles requires a deep dive into the Java Virtual Machine’s (JVM) internal mechanisms, particularly the role of class loaders and the garbage collector.

Understanding Java Class Loaders

At the heart of class loading and, consequently, class unloading in Java are class loaders. A class loader is a core component of the Java runtime environment that dynamically loads Java classes into the JVM. Each class in a Java application is loaded by an instance of a ClassLoader. This mechanism allows Java to extend its runtime environment dynamically, loading code from various sources like local file systems, JAR files, or even across a network. Without a firm grasp of how class loaders operate, comprehending class unloading becomes an uphill battle.

The hierarchical nature of class loaders is a fundamental concept. When the JVM needs a class, it delegates the loading task to its parent class loader. This process continues up the hierarchy until the Bootstrap ClassLoader is reached. If a class is not found by a parent, the child class loader then attempts to load it. This delegation model prevents classes from being loaded multiple times and ensures security by controlling which classes can access others. Different applications or components within the same JVM can use distinct class loaders, allowing for isolation and versioning of libraries.

The ClassLoader Hierarchy

Java’s class loading system typically involves three primary built-in class loaders, forming a clear hierarchy:

  • Bootstrap ClassLoader: This is the primordial class loader, built into the JVM itself. It loads core Java API classes (e.g., from rt.jar or modules in recent Java versions). It doesn’t have a parent class loader and is usually implemented in native code.
  • Extension ClassLoader: This loads classes from the Java extension directories (jre/lib/ext or platform-specific extensions). It’s a child of the Bootstrap ClassLoader.
  • Application ClassLoader (System ClassLoader): This loads classes from the application’s classpath (specified by the -classpath or CLASSPATH environment variable). It’s a child of the Extension ClassLoader and is the default class loader for applications.

Beyond these, developers can create custom class loaders to handle specific scenarios, such as loading classes from remote sources or implementing hot-swappable plugins. Each custom class loader adds another layer to this hierarchy, making the management of class lifecycles even more critical.

When Does Class Unloading Happen?

Unlike regular objects, which are garbage collected as soon as they are unreachable, Java classes are not unloaded simply because they are no longer referenced by active code. The primary condition for unloading classes in Java is that the ClassLoader instance that loaded them, along with all instances of the classes it loaded, must become unreachable and eligible for garbage collection. This is a critical distinction that often causes confusion and leads to memory leaks in complex Java applications.

For a class to be unloaded, three conditions must generally be met:

  1. All instances of the class must be garbage collected. No active objects of that type can exist on the heap.
  2. The java.lang.Class object representing the class itself must be garbage collected.
  3. The ClassLoader instance that originally loaded the class must become unreachable and eligible for garbage collection.

If any of these conditions are not met, the class (and potentially the class loader) will persist in memory, consuming resources. This is particularly problematic in environments where class loaders are dynamically created and destroyed, such as application servers deploying and undeploying web applications, or plugin systems loading and unloading extensions. Historically, the JVM stored class metadata in a special memory area called PermGen (Permanent Generation). When PermGen filled up, it often led to OutOfMemoryError: PermGen space. Since Java 8, PermGen has been replaced by Metaspace. Metaspace is still part of the native memory, but unlike PermGen, it can dynamically expand up to the available native memory (unless constrained by -XX:MaxMetaspaceSize). While this change mitigates some OOM errors, it doesn’t eliminate the need for proper class unloading; rather, it shifts the problem to potential native memory leaks if classloaders and their classes are not properly released. For more details on this transition, refer to Oracle’s documentation on JVM Ergonomics.

The Role of Garbage Collection in Class Unloading

The Java Garbage Collector (GC) plays an indirect yet crucial role in unloading classes in Java. While the GC primarily focuses on reclaiming memory from unreachable objects, it also, as a side effect, determines when class loaders themselves become unreachable. When a custom class loader is no longer referenced by any active part of the application, and all the classes it loaded have had their instances garbage collected, the class loader instance itself becomes eligible for GC. Only after the class loader is garbage collected can the metadata for the classes it loaded be removed from the Metaspace.

This dependency highlights why class unloading is not a direct action but rather a consequence of proper object lifecycle management. Developers cannot explicitly trigger class unloading. Instead, they must ensure that all references to class instances and, critically, to the class loader itself are cleared when they are no longer needed. This includes strong references from static fields, threads, JNI code, or other long-lived objects. If even a single strong reference to a loaded class or its class loader persists, neither the class nor the class loader can be garbage collected, leading to a memory leak.

Memory Leaks and Class Unloading Issues

Class-related memory leaks are particularly insidious because they can slowly consume native memory over time, leading to performance degradation and eventual OutOfMemoryError. Common scenarios include:

  • Lingering Thread Locals: Threads spawned by a dynamically loaded class that are not properly terminated can hold references to the class loader. Even if the application using the class loader is undeployed, these threads might keep the class loader alive.

  • Static Fields: Static fields in classes loaded by a custom class loader can hold references to objects that, in turn, hold references back to the class loader or other classes it loaded. These static references persist until the class loader itself is unloaded.

  • JNI References: Native code (JNI) can create global references to Java objects and classes. If these are not explicitly released when the component is undeployed, they can prevent class unloading.

  • Event Listeners/Callbacks: If a dynamically loaded class registers itself as a listener with a long-lived object (e.g., a system-wide service), and fails to unregister, the listener object (and thus its class) will be kept alive. For more Question & Answer :
    I have a custom class loader so that a desktop application can dynamically start loading classes from an AppServer I need to talk to. We did this since the amount of jars that are required to do this are ridiculous (if we wanted to ship them). We also have version problems if we don’t load the classes dynamically at run time from the AppServer library.

    Now, I just hit a problem where I need to talk to two different AppServers and found that depending on whose classes I load first I might break badly… Is there any way to force the unloading of the class without actually killing the JVM?

    Hope this makes sense

    The only way that a Class can be unloaded is if the Classloader used is garbage collected. This means, references to every single class and to the classloader itself need to go the way of the dodo.

    One possible solution to your problem is to have a Classloader for every jar file, and a Classloader for each of the AppServers that delegates the actual loading of classes to specific Jar classloaders. That way, you can point to different versions of the jar file for every App server.

    This is not trivial, though. The OSGi platform strives to do just this, as each bundle has a different classloader and dependencies are resolved by the platform. Maybe a good solution would be to take a look at it.

    If you don’t want to use OSGI, one possible implementation could be to use one instance of JarClassloader class for every JAR file.

    And create a new, MultiClassloader class that extends Classloader. This class internally would have an array (or List) of JarClassloaders, and in the defineClass() method would iterate through all the internal classloaders until a definition can be found, or a NoClassDefFoundException is thrown. A couple of accessor methods can be provided to add new JarClassloaders to the class. There is several possible implementations on the net for a MultiClassLoader, so you might not even need to write your own.

    If you instanciate a MultiClassloader for every connection to the server, in principle it is possible that every server uses a different version of the same class.

    I’ve used the MultiClassloader idea in a project, where classes that contained user-defined scripts had to be loaded and unloaded from memory and it worked quite well.

๐Ÿท๏ธ Tags: