On using `Class`es in Maps versus java.lang.ClassValue

Interesting – why? Mind, this code has been running for half a dozen years in production, so I have evidence that it works in practice on both JVM and JS. What are the downsides?

To quote from my own answer to the SO question I linked:

The best explanation of the purpose of this class is that it solves Java bug 6389107

There are many use cases where one wants to essentially have a Map<Class<?>, T> for some reason, but this causes all sorts of trouble since Class objects will then not be GC-able until the Map is. WeakHashMap, T> doesn’t solve the problem because very frequently, T references the class.

The bug above goes into a much more detailed explanation and contains example projects/code that face this problem.

ClassValue is the answer to this problem. A thread-safe, classloader loading/unloading safe way to associate data with a Class.

If you never need to have any classes in the map unloaded, or never reference the class from the value in the map, then a weak map will work ok.

Otherwise, you’ll prevent class unloading. If this is a library open to public use, its a pretty bad idea, you don’t know if your users need to do any dynamic classloader work (e.g. if they run inside osgi or something).
Otherwise, if it is for private use and you don’t need class unloading, its OK.

I’m assuming its not a problem for JS, but I’m not an expert there. It would probably be useful for Scala to have a class that handles this case so that one can do the right thing for the JVM and still be compatible with ScalaJS

Or we just implement ClassValue in Scala.js.

3 Likes

Okay, makes sense. I don’t think that’s a current concern in my case, but I’ve occasionally thought about pulling this DI system out into a library, so this is useful for me to know. Thanks!

I’m curious why there is a need for class unloading? Can’t you instead use a fresh instance of a custom classloader that ignores it? Or is it a memory consumption concern? And if it is important to you that a class is unloaded, how could you possibly guarantee that?

I would imagine that classes don’t make good map keys, because they can be pretty big, which makes calculating hash code or checking for equality fairly expensive.

I’m curious why there is a need for class unloading? Can’t you instead use a fresh instance of a custom classloader that ignores it? Or is it a memory consumption concern? And if it is important to you that a class is unloaded, how could you possibly guarantee that?

I personally haven’t had these problems, but I have written libraries where users cared about it.

The primary use case I recall was those writing components meant to be loaded/unloaded via OSGi. Perhaps some plugin needs to be updated in a live app and unload the old version and install a new one, and a failure to clear out the old one is a memory leak. I’m reminded of using ScalaIDE (Eclipse is OSGi) and having to restart a couple times a day because the Scala plugin keeps leaking its abandoned compiler instances. Whether that bug was/is due to this sort of leak or not is not my point – just that this is the sort of thing that leads to memory leaks that don’t tend to get tracked down quickly.

The OSGi ecosystem (which admitedly doesn’t overlap that much with the Scala ecosystem) depends on good classloader hygene. Various old-school application servers and some newer frameworks do too.

I would imagine that classes don’t make good map keys, because they can be pretty big, which makes calculating hash code or checking for equality fairly expensive.

A class is identified by the combination of its fully qualified name + its classloader instance. ‘com.foo.Foo’ is not equal to ‘com.foo.Foo’ from another classloader. At worst case this is a ref check + the name, but in practice is simply reference equality and system hash code – classes are singetons.

I have run into the class-as-key pattern in a few places in the past, and in a few it has been tempting to store the map in a static location like a Scala object. Serialization is a typical use case. But the consequences are problematic for multi-classloader worlds. Even if the map is not static, if it references classes from another classloader it can cause unintended retention.

I hope there is a TypeValue to map generic types to values, so that we can use it to cache type classes.