當我們分析內存泄漏的場景時,總會想到不能在內部類中做耗時操作,因為它會持有外部類的因為,導致外部類的實例在生命周期結束的時候沒有辦法及時釋放,這就造成了內存泄漏.
好像這就是一個公理一樣,就是人們說着說着就都認可它了,卻沒有人能說出個為什么.
今天我們就來分析一下為什么吧
首先來看一個例子
public class Outer {
private int count;
//匿名內部類1
private StaticInner si1 = new StaticInner(){
@Override
public void doAction() {
count++;
}
};
private StaticInner si2;
private Inner i3;
public void setInner(StaticInner inner) {
this.si2 = inner;
}
public Outer() {
i3 = new Inner();
//匿名內部類2
setInner(new StaticInner(){
@Override
public void doAction() {
super.doAction();
count++;
}
});
}
public void doAction() {
si1.doAction();
si2.doAction();
i3.doSomething();
}
/**
* 內部類
*/
private class Inner {
public void doSomething() {
count++;
}
}
/**
* 靜態內部類
*/
private static class StaticInner{
public void doAction() {
}
}
}
以上代碼概述了我們平時寫代碼時常見的幾種內部類和匿名內部類以及靜態內部類的寫法,並訪問了外部類的成員變量,當然靜態內部類沒法訪問,編譯器會報錯.
然后通過javac命令編譯一下.java源文件,得到幾個.class字節碼文件,這時如果你嘗試打開字節碼文件,會發現一堆亂碼,可以用jd-gui字節碼反編譯工具打開.
我們可以發現好幾個.class文件,一個一個來看
Outer$1.class
class Outer$1
extends Outer.StaticInner
{
Outer$1(Outer paramOuter)
{
super(null);
}
public void doAction()
{
Outer.access$108(this.this$0);
}
}
這個類代表我們聲明成員變量si1
的匿名內部類,可以看到它繼承自靜態內部類StaticInner
,它還定義了一個構造函數,並傳入了內部類的實例作為參數,雖然不知道super(null)具體實現,但是我們知道它要訪問paramOuter
實例的成員變量,必須得使用它的引用來訪問,所以它肯定是持有了外部類實例的引用.
Outer$2.class
class Outer$2
extends Outer.StaticInner
{
Outer$2(Outer paramOuter)
{
super(null);
}
public void doAction()
{
super.doAction();
Outer.access$108(this.this$0);
}
}
這個和上面提到的Outer$1.class
類似,所以這兩種匿名內部類是沒有任何本質區別的,不管是定義成員變量還是方法傳參.
Outer$Inner.class
class Outer$Inner
{
private Outer$Inner(Outer paramOuter) {}
public void doSomething()
{
Outer.access$108(this.this$0);
}
}
這是定義的內部類,可以看到編譯器也為它單獨生成了一個.class
文件,構造函數被定義為私有的,且同樣傳入了外部類的實例,所以它也是持有了外部類實例的引用.
Outer$StaticInner.class
class Outer$StaticInner
{
public void doAction() {}
}
這是靜態內部類編譯后的字節碼文件,編譯器並沒有為它添加額外的構造函數,所以它其實和我們的外部類沒有任何關系,這是寫在同一個.java
源文件中而已.
分析到這里,應該算可以證明為什么靜態內部類會持有外部類的實例了
在分析內存泄漏的時候,它應該是我們應該重點關注的對象.
由此我們也能夠明白java
代碼執行過程並不是我們所看的那樣,編譯器也做了很多工作.只是這一塊我們並不怎么關注.