天天看點

使用雙花括号初始化集合,可别這麼幹Double Brace Initialization should not be used

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

。當然這隻是表面,接下來我們從位元組碼的層面看看到底怎麼回事。

使用了匿名内部類

  • 雙花括号初始化的方法
使用雙花括号初始化集合,可别這麼幹Double Brace Initialization should not be used
  • 非雙花括号初始化的方法
使用雙花括号初始化集合,可别這麼幹Double Brace Initialization should not be used

編譯後産生的檔案

使用雙花括号初始化集合,可别這麼幹Double Brace Initialization should not be used

可以發現,多了一個内部類:A$1.class。

檢視内部類位元組碼

使用雙花括号初始化集合,可别這麼幹Double Brace Initialization should not be used
  1. 擁有一個外部類的成員變量
  2. 通過構造方法傳入了外部類對象的引用
  3. 将外部類對象的引用指派給成員變量
  4. 執行我們寫的put方法
  5. 内部類繼承于HashMap

發生了記憶體洩漏

使用非雙花括号初始化map的sandy被回收了,而使用雙花括号初始化map的bob卻沒有被回收。原因是b1的map和a1沒啥關系,a1=null後,根不可達,是以被回收了。b的map持有a的引用,是以a=null後,還有強引用,依然根可達,不能回收,最終發生記憶體洩漏。