當前位置: 妍妍網 > 碼農

Lambda 運算式到底會不會造成記憶體泄漏?

2024-02-29碼農

來源:blog.csdn.net/hbdatouerzi/article/details/122534134

👉 歡迎 ,你將獲得: 專屬的計畫實戰 / 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:因公眾號平台更改了推播規則,如果不想錯過內容,記得讀完點一下在看,加個星標,這樣每次新文章推播才會第一時間出現在你的訂閱列表裏。

    「在看」支持小哈呀,謝謝啦