Double Brace Initialization should not be used
前言
最近在修改sonar問題時,發現有人使用雙花括号初始化集合,提示可能發生記憶體洩漏。這種初始化方式倒是見過,隻知道是使用了匿名内部類,但沒有意識到這個問題。
實測
A
提供兩種Map的初始化方法,為了觀察是否被回收,重寫了finalize方法。
public class A {
private String name;
public A(String name) {
this.name = name;
}
public Map<String, String> getMap() {
return new HashMap<String, String>() {
private static final long serialVersionUID = -3309655755403147761L;
{
put("name", name);
}};
}
public Map<String, String> getMap1() {
return new HashMap<String, String>();
}
@Override
protected void finalize() throws Throwable {
System.out.println("Thread name: " + Thread.currentThread().getName() + " Object: " + this.name + " Gc happen");
super.finalize();
}
}
複制
B
有個map成員變量
public class B {
private Map<String, String> map;
public B(Map<String, String> map) {
this.map = map;
}
}
複制
Test
public class Test {
public static void main(String[] args) throws InterruptedException {
A a = new A("bob");
final B b = new B(a.getMap());
System.out.println("通路外部類對象的屬性:" + b.getMap().get("name"));
a = null;
System.gc();
Thread.sleep(1000);
A a1 = new A("sandy");
final B b1 = new B(a1.getMap1());
a1 = null;
System.gc();
Thread.sleep(1000);
}
}
複制
輸出
通路外部類對象的屬性:bob
Thread name: Finalizer Object: sandy Gc happen
複制
分析
匿名内部類持有外部類對象引用
雙花括号初始化時,可以直接使用外部類對象的成員
name
。當然這隻是表面,接下來我們從位元組碼的層面看看到底怎麼回事。
使用了匿名内部類
- 雙花括号初始化的方法
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAjM2EzLcd3LcJzLcJzdllmVldWYtl2Pn5GcukTZ5UGO4MTYiBDO5EWZ0ETNjZjYxcDNwI2M0YWY4ADZvwlM4kDNyYTOtUGall3LcVmdhNXLwRHdo9CXt92YucWbpRWdvx2Yx5yazF2Lc9CX6MHc0RHaiojIsJye.png)
- 非雙花括号初始化的方法
編譯後産生的檔案
可以發現,多了一個内部類:A$1.class。
檢視内部類位元組碼
- 擁有一個外部類的成員變量
- 通過構造方法傳入了外部類對象的引用
- 将外部類對象的引用指派給成員變量
- 執行我們寫的put方法
- 内部類繼承于HashMap
發生了記憶體洩漏
使用非雙花括号初始化map的sandy被回收了,而使用雙花括号初始化map的bob卻沒有被回收。原因是b1的map和a1沒啥關系,a1=null後,根不可達,是以被回收了。b的map持有a的引用,是以a=null後,還有強引用,依然根可達,不能回收,最終發生記憶體洩漏。