50

I have read somewhere that using the class instances as below is not a good idea as it might cause memory leaks. Can someone tell me if that is a valid statement? Or are there any problems using them this way?

Map<Class<?>,String> classToInstance = new HashMap();

classToInstance.put(String.class,"Test obj");
randy
  • 369
  • 4
  • 12
Aravind Yarram
  • 78,777
  • 46
  • 231
  • 327
  • I'm not sure it'd cause memory leaks... it just means the static version of String (used by its static methods) would be referenced by your map. – Powerlord Apr 12 '10 at 21:27
  • 2
    The memory-leak problems are (by example) when you hang a reference to a object or class from a longer-lived map. By example if in a web-app you register a JDBC driver from your war (DriverManager.ergisterDriver), the DriverManager class, loaded by the bootstrap classloader (not the webapp classloader) is holding a ref to your driver class. So when you unload the web app it cannot unload the webapp classloader (because of the hooked driver class) and none of the other webapp classes are unloaded. And that's not nice. – helios Apr 12 '10 at 21:34
  • I'd be curious where you heard this leak concept? Can you remember any details about where you heard this? Was it possibly in the context of an environment with multiple classloaders (e.g. web app container)? – Bert F Apr 12 '10 at 21:41
  • i dont really recollect where I've read it...otherwise I'd have linked to the article here – Aravind Yarram Apr 13 '10 at 01:47
  • @Pangea - your memory is correct. See my answer. – Stephen C Apr 13 '10 at 04:26
  • Watch the donut, not the hole. While the answer to "will this leak" has been answered I believe the answers are irrelevant to a running application. You empty the map when you are done, while your app is running you need them for processing and they will be in memory because of that fact alone. – Frank C. Feb 01 '11 at 05:21
  • @FrankC. If the map is a static variable then you will have possibility of having a leak regardless of "a running application". I assume you mean its not a problem if its an instance variable or stack variable. – Adam Gent Oct 15 '12 at 17:03

4 Answers4

28

Yes, you do need to be cautious! For example, if your code is running in a web container and you are in the habit of doing hot deployment of webapps, a retained reference to a single class object can cause a significant permgen memory leak.

This article explains the problem in detail. But in a nutshell, the problem is that each class contains a reference to its classloader, and each classloader contains references to every class that it has loaded. So if one class is reachable, all of them are.

The other thing to note is that if one of the classes that you are using as a key is reloaded then:

  1. The old and new versions of the class will not be equal.
  2. Looking up the new class will initially give a "miss".
  3. After you have added the new class to the map, you will now have two different map entries for the different versions of the class.
  4. This applies even if there is no code difference between the two versions of the class. They will be different simply because they were loaded by different classloaders.

From Java 8 - Permgen was removed. Do you think it is ok to use Class instance as HashMap key in any situations?

Be aware that you will still have a memory leak. Any dynamicly loaded class used in your HashMap (key or value) and (at least) other dynamically loaded classes will be kept reachable. This means the GC won't be able to unload / delete them.

What was previously a permgen leak is now a ordinary heap and metaspace storage leak. (Metaspace is where the class descriptors and code objects for the classes are kept.)

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • 2
    From Java 8 - Permgen was removed. Do you think it is ok to use Class instance as HashMap key in any situations? Thank you! – LHA Jan 08 '16 at 19:18
6

As Stephen C mentioned, the memory leak is indeed because of classloaders. But the problem is more acute than at first glance. Consider this:

mapkey --> class --> classloader --> all other classes defined by this classloader.

Furthermore,

class --> any static members, including static Maps e.g. caches.

A few such static caches can start adding up to serious amounts of memory lost whenever a webapp or some other dynamically (classloaded) loaded app is cycled.

There are several approaches to working around this problem. If you don't care about different 'versions' of the same class from different classloaders, then simply key based on Class.getName(), which is a java.lang.String.

Another option is to use java.util.WeakHashMap. This form of Map only maintains weak references to the keys. Weak references don't hold up GC, so they key won't cause a memory accumulation. However, the values are not referenced weakly. So if the values are for example instances of the classes used as keys, the WeakHashMap does not work.

Dilum Ranatunga
  • 13,254
  • 3
  • 41
  • 52
5

No, that's not a problem. As long as you were creating an instance of the class anyway, you're not using any more memory by holding a reference to the class itself.

Michael Myers
  • 188,989
  • 46
  • 291
  • 292
  • Interesting question. I can see how no additional memory is required here, but will the reference to a class object prevent the classToInstanceMap from being garbage collected though? – Chris Knight Apr 12 '10 at 21:28
  • 1
    It's not a problem... except the map instance is hanging from a longer-lived class or object than the class being used as a key. In that case the "key" class is not garbage-collected because of the map is still there... If the map is short-lived, or at least is not hanging from some JVM base class it's ok. – helios Apr 12 '10 at 21:31
  • 3
    @Chris Knight: No, reachability only goes forward. `classToInstanceMap` having a reference to `String.class` will prevent `String.class` from being collected, but not the other way around. `String.class` might refer to a type information block that is not eligible for collection anayway, but I don't know enough about the JVM to say that for sure. – Sam Harwell Apr 12 '10 at 21:33
  • @helios and @280Z28, thanks. Learn something new every day :) – Chris Knight Apr 12 '10 at 21:35
0

That depends where the reference classToInstance=new HashMap(); is defined.

A class points back to its class loader, and as a consequence, the class loader can not be garbage collected while a reference to the class exists. But if the references form a circle (or an unreachable cluster), this still works -- the GC knows how to deal with circular references.

parent class loader --> class loader <--> class  // no GC is possible
parent class loader     class loader <--> class  // circular references are GC'ed

So a reference to a class may prevent the class loader from being GC'ed only if the references come from an object/class in a parent class loader.

parent class loader     class loader <--> class  // no GC is possible
                    \-------------------/

That's what the sentence "any reference from outside the application to an object in the application of which the class is loaded by the application's classloader will cause a classloader leak" means in the article mentioned in Stephen C. answer.

But it's shouldn't be the case if the map is part of your application.

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
ewernli
  • 38,045
  • 5
  • 92
  • 123