Skip to content

Class unloading

Brian S. O'Neill edited this page Jun 3, 2021 · 25 revisions

In theory, any Java class can be unloaded when it's no longer referenced by anything, and the unloading is performed during garbage collection. In practice, classes tend to live for much longer than expected for two reasons:

  • Static initializers
  • Strong references within the class loader

Because a static initializer can have side effects, unloading a class and then loading it back in later might cause the side effect to be applied multiple times. This is mainly a problem when loading classes from jars or the file system, but for classes which are dynamically generated, such side effects can be avoided by not defining them in the first place.

Even still, classes cannot be unloaded until its class loader isn't strongly referenced by anything. This is because the class loader contains strong references to all of its classes. As a result, a reference to any class in the class loader prevents all of them from being unloaded.

With Cojen, dynamically generated classes are grouped together by package and parent class loader. Classes defined in the same package but with different parent loaders cannot access each other's package-private members. Additional control over the group boundary is possible by providing an opaque key object, and then package-private access isn't possible even with a common parent class loader.

In order for classes to be unloaded, group them accordingly, and then the entire group of classes can be unloaded when no longer referenced. In the extreme case, a distinct group can be used for each class, but this tends to have higher space overhead.

When making a class with a lookup, the class is defined in the class loader of the lookup class, and so there's less control over class unloading, unless the class loader happens to be managed by Cojen itself. If the lookup was obtained from a class which was loaded by the system class loader, then the generated class almost certainly will never be unloaded.

When testing that classes are being properly unloaded, be aware that soft references can cause seemingly unreferenced classes to remain even after running a full GC. Run with -XX:SoftRefLRUPolicyMSPerMB=0 to allow soft references to be cleared more aggressively, and run with -verbose:class to observe class unloading.

Hidden classes

Hidden classes are special in that they can be unloaded even when it's class loader is still strongly referenced. Although convenient, this does have some caveats. Hidden classes cannot directly refer to other hidden classes, which makes things quite difficult when making classes which depend on each other. Hidden classes are best used for implementing "leaf" classes that don't depend on any other generated classes.

Another caveat with hidden classes pertains to how exact constants are implemented. Calling Variable.setExact stores a reference to the constant in a WeakHashMap, keyed by the generated class, and the constant is removed from the map when first accessed by the generated class. If the constant happens to reference back to the hidden class itself (directly or indirectly), and the hidden class hasn't accessed the constant yet, then the hidden class cannot be unloaded until it's class loader isn't referenced anymore. This wouldn't be a problem if Java supported ephemerons. In practice, this isn't a concern with hidden "leaf" classes, because there's no real opportunity to set up an exact constant reference back to the class itself.

Clone this wiki locally