Java 使用 Class 实例作为 Map 键是最佳实践吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2625546/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is using the Class instance as a Map key a best practice?
提问by Aravind Yarram
I have read somewhere that using the class instances as below is not a good idea as they might cause memory leaks. Can someone tell me if if that is a valid statement? Or are they any problems using it this way?
我在某处读到使用下面的类实例不是一个好主意,因为它们可能会导致内存泄漏。有人能告诉我这是否是一个有效的陈述吗?或者他们以这种方式使用它有什么问题吗?
Map<Class<?>,String> classToInstance = new HashMap();
classToInstance.put(String.class,"Test obj");
采纳答案by Stephen C
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.
是的,您确实需要谨慎!例如,如果您的代码运行在 web 容器中,并且您习惯于热部署 webapps,那么保留对单个类对象的引用可能会导致显着的永久内存泄漏。
This articleexplains 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:
另一件要注意的事情是,如果您用作键的类之一被重新加载,则:
- The old and new versions of the class will not be equal.
- Looking up the new class will initially give a "miss".
- 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.
- This applies even if there is no codedifference 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?
从 Java 8 - Permgen 被删除。您认为在任何情况下都可以使用 Class 实例作为 HashMap 键吗?
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.
请注意,您仍然会有内存泄漏。HashMap 中使用的任何动态加载的类(键或值)和(至少)其他动态加载的类都将保持可达。这意味着 GC 将无法卸载/删除它们。
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.)
以前的 permgen 泄漏现在是普通的堆和元空间存储泄漏。(元空间是保存类的类描述符和代码对象的地方。)
回答by Michael Myers
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.
不,这不是问题。只要您创建了类的实例,就不会因为持有类本身的引用而使用更多内存。
回答by Dilum Ranatunga
As Stephen C mentioned, the memory leak is indeed because of classloaders. But the problem is more acute than at first glance. Consider this:
正如斯蒂芬 C 提到的,内存泄漏确实是由于类加载器。但问题比乍一看更为严重。考虑一下:
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.
每当 web 应用程序或其他一些动态(类加载)加载的应用程序循环时,一些这样的静态缓存可能会开始增加大量的内存丢失。
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
.
有几种方法可以解决这个问题。如果您不关心来自不同类加载器的同一类的不同“版本”,那么只需基于 键即可Class.getName()
,它是一个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 notreferenced weakly. So if the values are for example instances of the classes used as keys, the WeakHashMap
does not work.
另一种选择是使用java.util.WeakHashMap
. 这种形式的 Map 只维护对键的弱引用。弱引用不会阻止 GC,因此它们不会导致内存累积。但是,这些值并没有被弱引用。因此,如果这些值是用作键的类的实例,则WeakHashMap
不起作用。
回答by ewernli
That depends where the reference classToInstance=new HashMap();
is defined.
这取决于引用classToInstance=new HashMap();
的定义位置。
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.
一个类指向它的类加载器,因此,当对类的引用存在时,类加载器不能被垃圾收集。但是如果引用形成一个圆圈(或一个无法访问的集群),这仍然有效——GC 知道如何处理循环引用。
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 parentclass loader.
因此,仅当引用来自父类加载器中的对象/类时,对类的引用可能会阻止类加载器被 GC 。
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 articlementioned in Stephen C. answer.
这就是Stephen C. answer中提到的文章中“从应用程序外部对应用程序中由应用程序的类加载器加载该类的应用程序中的对象的任何引用将导致类加载器泄漏”这句话的意思。
But it's shouldn't be the case if the map is part of your application.
但如果地图是您的应用程序的一部分,情况就不应该如此。