在这篇文章里,我将描述一个遇到的一个低级的内存泄漏的问题
原生的 java6 和 7
仅用到了原生的JDK,无任何第三方依赖包。
假设你定义了 Map<String, Set<String>> 这样的一个变量. 变量 value 的类型只要求是一个 Collection 就行。
下面的方法初始化了一个 Map<String, Set<String>>:
1 2 3 4 5 6 7 8 9 10 11 12
private final Map<String, Set<String>> m_ids = new HashMap<String, Set<String>>( 4 );
private Set getSetByNameFaulty( final String id ) {
Set res = m_ids.get( id );
if ( res == null ) {
res = new HashSet( 1 );
m_ids.put( id, res );
}
return res;
}
下面方法检查了 map 中对应的 key 是否有值,如果有,直接返回 set,如果没有,则新建一个 set 并存到 map 中
1 2 3 4 5 6 7
private void populateJava67() {
getSetByNameFaulty( "id1" ).add( "property1" );
getSetByNameFaulty( "id1" ).add( "property2" );
getSetByNameFaulty( "id1" ).add( "property3" );
getSetByNameFaulty( "id2" ).add( "property1" );
}
接下来写一段能访问到 map 中数据的代码:
1 2 3 4
private boolean hasPropertyFaulty( final String id, final String property ) {
return getSetByNameFaulty( id ).contains( property );
}
这个方法在代码质量监测工具中没什么问题。 但是,它是有致命缺陷的: 如果你查询了一个不存在的Key,getSetByNameFaulty 这个方法会在 map 中创建一个空的 set. 这绝对有问题的. 我们应该控制 getSetByNameFaulty 何时写入数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
private Set getSetByName( final String id, final boolean isWrite ) {
Set res = m_ids.get( id );
if ( res == null ) {
if ( isWrite ) {
res = new HashSet( 1 );
m_ids.put( id, res );
} else {
res = Collections.emptySet();
}
}
return res;
}
private boolean hasProperty( final String id, final String property ) {
return getSetByName( id, false ).contains( property );
}
private void populateJava67Better() {
getSetByName( "id1", true ).add( "property1" );
getSetByName( "id1", true ).add( "property2" );
getSetByName( "id1", true ).add( "property3" );
getSetByName( "id2", true ).add( "property1" );
}
Java 8
java 8 在 Map 接口中新增了下面的方法(实际上还新增了很多方法):
1
V getOrDefault(Object key, V defaultValue)
这个方法返回了 map 中的value,如果map中不存在key,则返回默认值. 这个 Map 结构在这个方法调用之后并不会改变,它让我们可以简单明了的访问 Map 中的数据
1 2 3 4
private boolean hasPropertyJava8( final String id, final String property ) {
return m_ids.getOrDefault( id, Collections.emptySet() ).contains( property );
}
Google Guava
Google Guava library 定义了一个 MultiMap 接口,并提供了一些列的实现。它的实现类的数量差不多跟JDK 的 Collections 实现类的数量一样了
MultiMap.get 定义如下
1
Collection get(K key)
如果key存在,它会返回所有的值,如果key 不存在,它会返回一个空的Set. 下面用 Google Guava 来实现获取 map 中的 set,可以看出,我们也不用关注 map 中的 set
1 2 3 4 5 6 7 8 9 10 11
private final Multimap m_idsMM = HashMultimap.create();
private void addPropertyGuava( final String id, final String property ) {
m_idsMM.put( id, property );
}
private boolean hasPropertyGuava( final String id, final String property ) {
return m_idsMM.get( id ).contains( property );
}
总结
- 当实现一个 multilevelMap,非常容易造成内存泄漏的问题. 开发者应该足够重视,并且最外层的 map 要将读写数据的操作分开
- 新语言或者第三方工具,像 java 8 或者 Google Guava,已经提供了足够多的选择可以让你在实现 multilevelMap 时避免内存泄漏
翻译来源:http://java-performance.info/possible-memory-leak-in-multimap/
微信扫码关注: