我遇到了一段代码,我确信这会导致无意的内存泄漏:
Object user = getUser("Bob");
Map<String, WeakReference<Object>> map = new HashMap<>();
map.put("Bob", new WeakReference( user ) );
此地图的目的是缓存对象,并在GC不再被强烈引用时自动从地图中清除它们。
然而,我看到它的方式是,如果键也不是弱引用,那么一旦Object被GC,在哈希映射中仍然会有一个条目,其中键指向null。因此,地图仍将包含相同数量的行,只是所有行都指向空值。
因此,在上面的示例中,一旦释放了对user
的所有强引用,并且GC破坏了Object
,地图中的条目将等同于:
map.put("Bob", null );
因此,除非有一个清理例程刷新所有具有空值的键,否则我的地图将继续增长。
那么问题就变成了如何解决这个问题?是否有我可以使用的地图构造,如果值被销毁,它将自动刷新我的条目?
我打算做类似的事情:
Object user = getUser("Bob");
Map<String, WeakReference<Object>> map = new WeakHashMap<>();
map.put(user.getUsername(), new WeakReference( user ) );
但这似乎是一个非常有限的用例,我的密钥必须是从我的值中检索的对象。使用WeakHashMap
我的键不能是String常量(即:“Bob”),否则将不会有任何其他引用,GC将从我的地图中清除该对象。
是否有其他缓存构造提供所有这些功能?
答案 0 :(得分:2)
如果密钥也不是弱引用,那么一旦对象被GC编辑,哈希映射中仍然会有一个条目指向null。
键指向非空WeakReference
,指向空指示物。
因此,地图仍将包含相同数量的行,只是所有行都指向空值。
不,见上文。
解决方案是使用WeakHashMap<String, WeakReference<Object>>
,其背景活动通过ReferenceQueue
发现正在收集的弱密钥,并删除相应的映射。更好的是,WeakHashMap<String, WeakReference<User>>
。但是:
将此描述为内存泄漏有点丰富。 Map本身就是内存泄漏。如果您不想引用您的对象,请不要将它们存储在地图中。
答案 1 :(得分:1)
你的推理是正确的。
Map<String,WeakReference<Object>>
用于&#34;延迟&#34;键不会出现问题,因为它们比存储在地图中的对象小很多。在这样的情况下,将大量的键映射到清空的弱引用并不会使系统的内存资源足够紧张,以便您注意。
您可以使用WeakHashMap<K,V>
作为您希望收集密钥垃圾的情况,但您绝对正确地使用String
常量:这确实会破坏目的。通常,您使用的自定义键类型会使用引用相等性覆盖equals
。
答案 2 :(得分:1)
你是对的,因为对象的集合不会删除映射,但是,结果不等于
map.put("Bob", null );
它将等同于
map.put("Bob", new WeakReference<>(null) );
所以你不仅要有一个悬空的入口实例,还要有一个悬空的WeakReference
实例。
使用时
Map<String, WeakReference<User>> map = new WeakHashMap<>();
User user = getUser("Bob");
map.put(user.getUsername(), new WeakReference( user ) );
假设user.getUsername()
返回对User
对象中存储的字符串实例的引用,以确保只要User
为User
,就可以保持强大的可达性,从而获得所需的语义强烈可达。
我没有看到任何限制。由于存储在User
中的字符串实例确实存在,因此只要User u = map.get("Bob");
实例存在,就不会引用与map key完全相同的字符串实例的开销。您仍然可以使用字符串常量作为查找键ala String.hashCode()
,因为它们的相等性仍然是根据String.equals()
和put
确定的。如果"Bob"
使用字符串常量WeakHashMap
进行映射,则映射通常会持续至少只要包含常量的代码处于活动状态(以及使用该常量的每个其他代码)在这一生中相同的文字),可能是整个应用程序。但是,使用不同的字符串实例作为关键字而不是存储在指示对象中的意义何在?
请注意,public class UnicodeFormatter {
static public String charToHex(char c) {
// Returns hex String representation of char c
byte hi = (byte) (c >>> 8);
byte lo = (byte) (c & 0xff);
return byteToHex(hi) + byteToHex(lo);
}
}
for (int x = 0; x < myString.length(); x++){
System.out.println(UnicodeFormatter.charToHex(myString.charAt(x)) + " " + myString.charAt(x));
}
/*Print
00c3 Ã
fffd ?
00c3 Ã
2018 ‘
0049 I
0047 G
004f O
*/
必须处理同一问题,不会自动删除条目。它必须使用ReferenceQueue
来发现何时收集了引用对象以从表中删除其关联的条目。每当你调用一个方法就会发生This cleanup,所以当你不调用它上面的方法时,表就不会得到清理,但是因为每次插入都会发生这种清理,所以你可以防止不断增长场景。