java踩坑记之双花括号初始化实例导致内存泄露
问题描述
先来看一段代码:
public class DoubleBracesTest { private String key = "key"; private String value="value"; public Map<String, String> test(String[] args){ Map<String, String> map = new HashMap() {{ put("k", "v"); put(key,value); }}; return map; } }
通过javac编译后,生成文件:DoubleBracesTest.class 和 DoubleBracesTest$1.class,确认上面的代码中的"{{"的方式写法,采用了内部类来实现的。
用IDEA查看 DoubleBracesTest$1.class :
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // import java.util.HashMap; class DoubleBracesTest$1 extends HashMap { DoubleBracesTest$1(DoubleBracesTest var1) { this.this$0 = var1; this.put("k", "v"); this.put(this.this$0.key, this.this$0.value); } }
其中的 this.this$0 = var1 代表内部类持有了外部类的引用。
对应的字节码:
字节码中的 putfield这一行,这里表示有一个对DoubleBracesTest的引用被存在了 this$0 中,也就是说它持有了外部类的对象。
test方法返回一个map,如果被其他对象的属性所引用,GC时便不会回收此对象,从而导致内存泄漏!这个也是非静态内部类的主要缺点。
非静态内部类的优点
非静态匿名内部类持有外部类可以总结为以下两个作用 :
1.当匿名内部类只在外部类(主类)中使用时,匿名内部类可以让外部不知道它的存在,从而减少了代码的维护工作。
2.当匿名内部类持有外部类时,它就可以直接使用外部类中的变量了,这样可以很方便的完成调用。
改进方法
1、上述调用方法改成static方法
匿名内部类是静态的之后,它所引用的对象或属性也必须是静态的了,因此就可以直接从 JVM 的 Method Area(方法区)获取到引用而无需持久外部对象了,也就不会持有外部类的引用了。
但是后期难保不会有人将 static 关键字删掉,那样问题又会出现了!
2、
使用集合工厂的 of
方法替代
Map map = new HashMap() {{ put("k1", "v1"); put("k2", "v2"); }};
替换成:
Map<String, String> map= Map.of("k1", "v1", "k2", "v2");
List<String> list = new ArrayList() {{
add("aaa");
add("bbb");
}};
替换成:
List<String> list = new ArrayList<String>(Arrays.asList("aaa","bbb"));
Stream API 替代
List<String> list = new ArrayList() {{ add("aaa"); add("bbb"); }};
替换成
List<String> list = Stream.of("aaa", "bbb").collect(Collectors.toList());
参考
https://www.ripjava.com/article/1291630596325408
https://www.cnblogs.com/wenbronk/p/7000643.html
https://cloud.tencent.com/developer/article/1632486
https://cloud.tencent.com/developer/article/1179625
https://hacpai.com/article/1498563483898
作者:Eric Li
出处:http://www.cnblogs.com/ericli-ericli/
除转载文章外,随笔版权归作者和博客园所有,欢迎转载,转载请标明出处。
如果您觉得本篇博文对您有所收获,觉得作者还算用心,请点击右下角的 [推荐],谢谢!