CSDN|本来想用“{{”秀一波,结果却导致了内存溢出!( 二 )

CSDN|本来想用“{{”秀一波,结果却导致了内存溢出!
本文插图
以上代码的执行结果为:classcom.example.DoubleBracket从以上程序输出结果可以看出:匿名内部类持有了外部类的引用 , 因此我们才可以使用$0 正常获取到外部类 , 并输出相关的类信息 。什么情况会导致内存泄漏?当我们把以下正常的代码:public void createMap { Map map = new HashMap {{ put("map1", "value1"); put("map2", "value2"); put("map3", "value3"); }}; // 业务处理....}改为下面这个样子时 , 可能会造成内存泄漏:public Map createMap { Map map = new HashMap {{ put("map1", "value1"); put("map2", "value2"); put("map3", "value3"); }}; return map;}为什么用了「可能」而不是「一定」会造成内存泄漏? 这是因为当此 map 被赋值为其他类属性时 , 可能会导致 GC 收集时不清理此对象 , 这时候才会导致内存泄漏 。 可以关注我「Java中文社群」后面会专门写一篇关于此问题的文章 。如何保证内存不泄露?要想保证双花括号不泄漏 , 办法也很简单 , 只需要将 map 对象声明为 static 静态类型的就可以了 , 代码如下:public static Map createMap { Map map = new HashMap {{ put("map1", "value1"); put("map2", "value2"); put("map3", "value3"); }}; return map;}什么?你不相信! 没关系 , 我们用事实说话 , 使用以上代码 , 我们重新编译一份字节码 , 查看匿名类的内容如下:javap -c DoubleBracket\$1.classCompiled from "DoubleBracket.java"class com.example.DoubleBracket$1 extends java.util.HashMap { com.example.DoubleBracket$1; Code: 0: aload_0 1: invokespecial #1 // Method java/util/HashMap."<init>":V 4: aload_0 5: ldc #7 // String map1 7: ldc #9 // String value1 9: invokevirtual #11 // Method put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; 12: pop 13: aload_0 14: ldc #17 // String map2 16: ldc #19 // String value2 18: invokevirtual21: pop 22: aload_0 23: ldc #21 // String map3 25: ldc #23 // String value3 27: invokevirtual30: pop 31: return}从这次的代码我们可以看出 , 已经没有 putfield 关键字这一行了 , 也就是说静态匿名类不会持有外部对象的引用了 。为什么静态内部类不会持有外部类的引用?原因其实很简单 , 因为匿名内部类是静态的之后 , 它所引用的对象或属性也必须是静态的了 , 因此就可以直接从JVM 的 Method Area(方法区)获取到引用而无需持久外部对象了 。双花括号的替代方案即使声明为静态的变量可以避免内存泄漏 , 但依旧不建议这样使用 , 为什么呢? 原因很简单 , 项目一般都是需要团队协作的 , 假如那位老兄在不知情的情况下把你的 static 给删掉呢?这就相当于设置了一个隐形的“坑” , 其他不知道的人 , 一不小心就跳进去了 , 所以我们可以尝试一些其他的方案 , 比如 Java8中的 Stream API 和 Java9 中的集合工厂等 。替代方案 1:Stream使用 Java8 中的 Stream API 替代 , 示例如下 。 原代码:List<String> list = new ArrayList {{ add("Java"); add("Redis");}};替代代码:List<String> list = Stream.of("Java", "Redis").collect(Collectors.toList);替代方案 2:集合工厂使用集合工厂的 of 方法替代 , 示例如下 。 原代码:Map map = new HashMap {{ put("map1", "value1"); put("map2", "value2");}};替代代码:Map map = Map.of("map1", "Java", "map2", "Redis");显然使用 Java9 中的方案非常适合我们 , 简单又酷炫 , 只可惜我们还在用 Java6...6...6... 心碎了一地 。总结本文我们讲了双花括号初始化因为会持有外部类的引用 , 从而可以会导致内存泄漏的问题 , 还从字节码以及反射的层面演示了这个问题 。要想保证双花括号初始化不会出现内存泄漏的办法也很简单 , 只需要被 static 修饰即可 , 但这样做还是存在潜在的风险 , 可能会被某人不小心删除掉 , 于是我们另寻它道 , 发现了可以使用 Java8 中的 Stream 或Java9 中的集合工厂 of 方法替代“{{” 。参考 & 鸣谢 https://www.ripjava.com/article/1291630596325408 https://cloud.tencent.com/developer/article/1179625 https://hacpai.com/article/1498563483898


推荐阅读