👉 歡迎 ,你將獲得: 專屬的計畫實戰 / Java 學習路線 / 一對一提問 / 學習打卡 / 贈書福利
全棧前後端分離部落格計畫 1.0 版本完結啦,2.0 正在更新中 ... , 演示連結 : http://116.62.199.48/ ,全程手摸手,後端 + 前端全棧開發,從 0 到 1 講解每個功能點開發步驟,1v1 答疑,直到計畫上線。 目前已更新了219小節,累計35w+字,講解圖:1492張,還在持續爆肝中.. 後續還會上新更多計畫,目標是將Java領域典型的計畫都整一波,如秒殺系統, 線上商城, IM即時通訊,Spring Cloud Alibaba 等等,
1 背景
2 匿名內部類 VS Lambda 運算式
匿名內部類
Lambda 運算式
3 結論
1 背景
匿名內部類會持有外部類的參照,因此有造成記憶體泄漏的風險;
那麽Lambda 運算式是否會造成記憶體泄漏呢?
2 匿名內部類 VS Lambda 運算式
我們新建一個類TestInner,其中test方法裏麵包含一個Lambda運算式,test1方法裏麵包含一個匿名內部類
public class TestInner {
public void test(){
new Thread(()->{
Log.i("測試","dddd");
}).start();
}
public void test1(){
new Thread(new Runnable() {
@Override
public void run() {
Log.i("測試","dddd1");
}
}).start();
}
}
我們將其編譯成apk,然後檢視編譯後的產物
匿名內部類
首先來看匿名內部類。
我們發現test1方法編譯產物如下:
.method public test1()V
.registers 3
.line 14
new-instance v0, Ljava/lang/Thread;
new-instance v1, Lcom/example/jnihelper/TestInner$1;
invoke-direct {v1, p0}, Lcom/example/jnihelper/TestInner$1;-><init>(Lcom/example/jnihelper/TestInner;)V
invoke-direct {v0, v1}, Ljava/lang/Thread;-><init>(Ljava/lang/Runnable;)V
.line 19
invoke-virtual {v0}, Ljava/lang/Thread;->start()V
.line 20
return-void
.end method
從中可以發現,test1方法裏面呼叫了
TestInner$1
的init方法,接著我們可以從編譯產物當中找到
TestInner$1
接著我們檢視
TestInner$1
的內容,init方法會將TestInner以參數的形式傳入
# direct methods
.method constructor <init>(Lcom/example/jnihelper/TestInner;)V
.registers 2
.param p1, "this$0"# Lcom/example/jnihelper/TestInner;
.line 14
iput-object p1, p0, Lcom/example/jnihelper/TestInner$1;->this$0:Lcom/example/jnihelper/TestInner;
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
# virtual methods
.method public run()V
.registers 3
.line 17
const-string v0, "\u6d4b\u8bd5"
const-string v1, "dddd1"
invoke-static {v0, v1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
.line 18
return-void
.end method
所以匿名內部類會持有外部類的參照,因此會有記憶體泄漏的風險。
那麽Lambda 運算式呢?
Lambda 運算式
我們先來看下含有Lambda運算式的test方法的編譯產物
.method static synthetic Lambda運算式()V
.registers 2
.line 9
const-string v0, "\u6d4b\u8bd5"
const-string v1, "dddd"
invoke-static {v0, v1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
.line 10
return-void
.end method
# virtual methods
.method public test()V
.registers 3
.line 8
new-instance v0, Ljava/lang/Thread;
sget-object v1, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;->INSTANCE:Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;
invoke-direct {v0, v1}, Ljava/lang/Thread;-><init>(Ljava/lang/Runnable;)V
.line 10
invoke-virtual {v0}, Ljava/lang/Thread;->start()V
.line 11
return-void
.end method
我們可以看到test方法裏面呼叫了
com/example/jnihelper/TestInner$$ExternalSyntheticLambda0
開啟
TestInner$$ExternalSyntheticLambda0
,我們發現最後執行的run方法
會呼叫TestInner的
lambda$test$0
方法,而該方法是一個靜態方法。
# direct methods
.method static synthetic constructor <clinit>()V
.registers 1
new-instance v0, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;
invoke-direct {v0}, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;-><init>()V
sput-object v0, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;->INSTANCE:Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;
return-void
.end method
.method private synthetic constructor <init>()V
.registers 1
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
# virtual methods
.method public final run()V
.registers 1
invoke-static {}, Lcom/example/jnihelper/TestInner;->lambda$test$0()V
return-void
.end method
可以看出此時Lambda運算式並未持有外部類的參照,因而沒有造成記憶體泄漏。
然而真的是這樣嗎?
我們再回過頭來看下TestInner的Lambda 運算式實作,可以看出,內部確實沒有用到外部類。
public void test(){
new Thread(()->{
Log.i("測試","dddd");
}).start();
}
那麽,我們如果手動參照呢?
public class TestInner {
private String helloInner = "helloIIIII";
public void test() {
new Thread(() -> {
Log.i("測試", "dddd");
Log.i("測試", TestInner.this.helloInner);//手動顯示參照
}).start();
}
...
}
編譯之後檢視其編譯產物
# virtual methods
.method public synthetic lambda$test$0$com-example-jnihelper-TestInner()V
.registers 3
.line 11
const-string v0, "\u6d4b\u8bd5"
const-string v1, "dddd"
invoke-static {v0, v1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
.line 12
iget-object v1, p0, Lcom/example/jnihelper/TestInner;->helloInner:Ljava/lang/String;
invoke-static {v0, v1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
.line 13
return-void
.end method
.method public test()V
.registers 3
.line 10
new-instance v0, Ljava/lang/Thread;
new-instance v1, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;
invoke-direct {v1, p0}, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;-><init>(Lcom/example/jnihelper/TestInner;)V
invoke-direct {v0, v1}, Ljava/lang/Thread;-><init>(Ljava/lang/Runnable;)V
.line 13
invoke-virtual {v0}, Ljava/lang/Thread;->start()V
.line 14
return-void
.end method
我們發現對應的實作方法
lambda$test0 00com-example-jnihelper-TestInner
已經變成非靜態方法了,
而在test方法裏面,呼叫了
TestInner$$ExternalSyntheticLambda0
的init方法,並將外部類當作參數傳入,
我們再來看
TestInner$$ExternalSyntheticLambda0
的實作,可以發現TestInner會在init方法中當作參入傳入,並當作其成員變量。
. class public final synthetic Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;
.super Ljava/lang/Object;
# interfaces
.implements Ljava/lang/Runnable;
# instance fields
.field public final synthetic f$0:Lcom/example/jnihelper/TestInner;
# direct methods
.method public synthetic constructor <init>(Lcom/example/jnihelper/TestInner;)V
.registers 2
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
iput-object p1, p0, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;->f$0:Lcom/example/jnihelper/TestInner;
return-void
.end method
# virtual methods
.method public final run()V
.registers 2
iget-object v0, p0, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;->f$0:Lcom/example/jnihelper/TestInner;
invoke-virtual {v0}, Lcom/example/jnihelper/TestInner;->lambda$test$0$com-example-jnihelper-TestInner()V
return-void
.end method
所以,可以看出,如果在Lambda 運算式中需顯示的呼叫外部類,才會持有外部類的參照。
3 結論
匿名內部類會持有外部類的參照,有造成記憶體泄漏的風險;
Lambda 運算式,若內部沒有參照外部類,則不會持有外部類;若內部參照到了外部類,則會持有外部類。
👉 歡迎 ,你將獲得: 專屬的計畫實戰 / Java 學習路線 / 一對一提問 / 學習打卡 / 贈書福利
全棧前後端分離部落格計畫 1.0 版本完結啦,2.0 正在更新中 ... , 演示連結 : http://116.62.199.48/ ,全程手摸手,後端 + 前端全棧開發,從 0 到 1 講解每個功能點開發步驟,1v1 答疑,直到計畫上線。 目前已更新了219小節,累計35w+字,講解圖:1492張,還在持續爆肝中.. 後續還會上新更多計畫,目標是將Java領域典型的計畫都整一波,如秒殺系統, 線上商城, IM即時通訊,Spring Cloud Alibaba 等等,
1.
2.
3.
4.
最近面試BAT,整理一份面試資料【Java面試BATJ通關手冊】,覆蓋了Java核心技術、JVM、Java並行、SSM、微服務、資料庫、數據結構等等。
獲取方式:點「在看」,關註公眾號並回復 Java 領取,更多內容陸續奉上。
PS:因公眾號平台更改了推播規則,如果不想錯過內容,記得讀完點一下「在看」,加個「星標」,這樣每次新文章推播才會第一時間出現在你的訂閱列表裏。
點「在看」支持小哈呀,謝謝啦