当前位置: 欣欣网 > 码农

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:因公众号平台更改了推送规则,如果不想错过内容,记得读完点一下在看,加个星标,这样每次新文章推送才会第一时间出现在你的订阅列表里。

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