做商城网站产品怎么分布,企业品牌网站建设怎么做,湖北联诺建设网站,做捐款网站这是我的第 200 期分享作者 | 王磊来源 | Java中文社群#xff08;ID#xff1a;javacn666#xff09;转载请联系授权#xff08;微信ID#xff1a;GG_Stone#xff09;生活中的尴尬无处不在#xff0c;有时候你只是想简单的装一把#xff0c;但某些“老同志”总是在不… 这是我的第 200 期分享作者 | 王磊来源 | Java中文社群IDjavacn666转载请联系授权微信IDGG_Stone生活中的尴尬无处不在有时候你只是想简单的装一把但某些“老同志”总是在不经意之间给你无情的一脚踹得你简直无法呼吸。但谁让咱年轻呢吃亏要趁早前路会更好。喝了这口温热的鸡汤咱们来聊聊是怎么回事。事情是这样的在一个不大不小的项目中小王写下了这段代码MapString, String map new HashMap() {{put(map1, value1);put(map2, value2);put(map3, value3);
}};
map.forEach((k, v) - {System.out.println(key: k value: v);
});
本来是用它来替代下面这段代码的MapString, String map new HashMap();
map.put(map1, value1);
map.put(map2, value2);
map.put(map3, value3);
map.forEach((k, v) - {System.out.println(key: k value: v);
});
两块代码的执行结果也是完全一样的key:map3 value:value3key:map2 value:value2key:map1 value:value1所以小王正在得意的把这段代码介绍给部门新来的妹子小甜甜看却不巧被正在经过的老张也看到了。老张本来只是想给昨天的枸杞再续上一杯 85° 的热水但说来也巧刚好撞到了一次能在小甜甜面前秀技术的一波机会于是习惯性的整理了一下自己稀疏的秀发便开启了 diss 模式。“小王啊你这个代码问题很大啊”“怎么能用双花括号初始化实例呢”此时的小王被问的一脸懵逼内心有无数个草泥马奔腾而过心想你这头老牛竟然也和我争这颗嫩草但内心却有一种不祥的预感感觉自己要输瞬间羞涩的不知该说啥只能红着小脸轻轻的“嗯”了一声。老张“使用双花括号初始化实例是会导致内存溢出的啦侬不晓得嘛”小王沉默了片刻只是凭借着以往的经验来看这“老家伙”还是有点东西的于是敷衍的“哦~”了一声仿佛自己明白了怎么回事一样其实内心仍然迷茫的一匹为了不让其他同事发现只得这般作态。于是片刻的敷衍待老张离去之后才悄悄的打开了 Google默默的搜索了一下。小王哦原来如此......双花括号初始化分析 首先我们来看使用双花括号初始化的本质是什么以我们这段代码为例MapString, String map new HashMap() {{put(map1, value1);put(map2, value2);put(map3, value3);
}};
这段代码其实是创建了匿名内部类然后再进行初始化代码块。这一点我们可以使用命令 javac 将代码编译成字节码之后发现我们发现之前的一个类被编译成两个字节码.class文件如下图所示我们使用 Idea 打开 DoubleBracket$1.class 文件发现import java.util.HashMap;class DoubleBracket$1 extends HashMap {DoubleBracket$1(DoubleBracket var1) {this.this$0 var1;this.put(map1, value1);this.put(map2, value2);}
}
此时我们可以确认它就是一个匿名内部类。那么问题来了匿名内部类为什么会导致内存溢出呢匿名内部类的“锅” 在 Java 语言中非静态内部类会持有外部类的引用从而导致 GC 无法回收这部分代码的引用以至于造成内存溢出。思考 1为什么要持有外部类这个就要从匿名内部类的设计说起了在 Java 语言中非静态匿名内部类的主要作用有两个。1、当匿名内部类只在外部类主类中使用时匿名内部类可以让外部不知道它的存在从而减少了代码的维护工作。2、当匿名内部类持有外部类时它就可以直接使用外部类中的变量了这样可以很方便的完成调用如下代码所示public class DoubleBracket {private static String userName 磊哥;public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {MapString, String map new HashMap() {{put(map1, value1);put(map2, value2);put(map3, value3);put(userName, userName);}};}
}
从上述代码可以看出在 HashMap 的方法内部可以直接使用外部类的变量 userName。思考 2它是怎么持有外部类的关于匿名内部类是如何持久外部对象的我们可以通过查看匿名内部类的字节码得知我们使用 javap -c DoubleBracket\$1.class 命令进行查看其中 $1 为以匿名类的字节码字节码的内容如下javap -c DoubleBracket\$1.class
Compiled from DoubleBracket.java
class com.example.DoubleBracket$1 extends java.util.HashMap {final com.example.DoubleBracket this$0;com.example.DoubleBracket$1(com.example.DoubleBracket);Code:0: aload_01: aload_12: putfield #1 // Field this$0:Lcom/example/DoubleBracket;5: aload_06: invokespecial #7 // Method java/util/HashMap.init:()V9: aload_010: ldc #13 // String map112: ldc #15 // String value114: invokevirtual #17 // Method put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;17: pop18: aload_019: ldc #21 // String map221: ldc #23 // String value223: invokevirtual #17 // Method put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;26: pop27: return
}
其中关键代码的在 putfield 这一行此行表示有一个对 DoubleBracket 的引用被存入到 this$0 中也就是说这个匿名内部类持有了外部类的引用。如果您觉得以上字节码不够直观没关系我们用下面的实际的代码来证明一下import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;public class DoubleBracket {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {Map map new DoubleBracket().createMap();// 获取一个类的所有字段Field field map.getClass().getDeclaredField(this$0);// 设置允许方法私有的 private 修饰的变量field.setAccessible(true);System.out.println(field.get(map).getClass());}public Map createMap() {// 双花括号初始化Map map new HashMap() {{put(map1, value1);put(map2, value2);put(map3, value3);}};return map;}
}
当我们开启调试模式时可以看出 map 中持有了外部对象 DoubleBracket如下图所示以上代码的执行结果为class com.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.class
Compiled from DoubleBracket.java
class com.example.DoubleBracket$1 extends java.util.HashMap {com.example.DoubleBracket$1();Code:0: aload_01: invokespecial #1 // Method java/util/HashMap.init:()V4: aload_05: ldc #7 // String map17: ldc #9 // String value19: invokevirtual #11 // Method put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;12: pop13: aload_014: ldc #17 // String map216: ldc #19 // String value218: invokevirtual #11 // Method put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;21: pop22: aload_023: ldc #21 // String map325: ldc #23 // String value327: invokevirtual #11 // Method put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;30: pop31: return
}
从这次的代码我们可以看出已经没有 putfield 关键字这一行了也就是说静态匿名类不会持有外部对象的引用了。为什么静态内部类不会持有外部类的引用原因其实很简单因为匿名内部类是静态的之后它所引用的对象或属性也必须是静态的了因此就可以直接从 JVM 的 Method Area方法区获取到引用而无需持久外部对象了。双花括号的替代方案 即使声明为静态的变量可以避免内存泄漏但依旧不建议这样使用为什么呢原因很简单项目一般都是需要团队协作的假如那位老兄在不知情的情况下把你的 static 给删掉呢这就相当于设置了一个隐形的“坑”其他不知道的人一不小心就跳进去了所以我们可以尝试一些其他的方案比如 Java8 中的 Stream API 和 Java9 中的集合工厂等。替代方案 1Stream使用 Java8 中的 Stream API 替代示例如下。原代码ListString list new ArrayList() {{add(Java);add(Redis);
}};
替代代码ListString 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 中的方案非常适合我们简单又酷炫只可惜我们还在用 Java 6...6...6... 心碎了一地。总结 本文我们讲了双花括号初始化因为会持有外部类的引用从而可以会导致内存泄漏的问题还从字节码以及反射的层面演示了这个问题。要想保证双花括号初始化不会出现内存泄漏的办法也很简单只需要被 static 修饰即可但这样做还是存在潜在的风险可能会被某人不小心删除掉于是我们另寻它道发现了可以使用 Java8 中的 Stream 或 Java9 中的集合工厂 of 方法替代“{{”。最后的话看在我努力编故事的份上点个「在看」再走呗参考 鸣谢https://www.ripjava.com/article/1291630596325408https://cloud.tencent.com/developer/article/1179625https://hacpai.com/article/1498563483898往期推荐
if快还是switch快解密switch背后的秘密HashMap 的 7 种遍历方式与性能分析「修正篇」关注公众号发送”进群“磊哥拉你进读者群。