查看原文
其他

Lambda 表达式到底会不会造成内存泄漏?

小哈学Java 2024-04-16

来源: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 等等,戳我加入学习,已有1000+小伙伴加入(早鸟价超低)


  • 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 等等,戳我加入学习,已有1000+小伙伴加入(早鸟价超低)



1. 我的私密学习小圈子~

2. 服务限流,我有6种实现方式…

3. 京东开源,牛逼哄哄的 JD-hotkey 到底有多强悍?

4. 动态更改 Spring 定时任务 Cron 表达式的优雅方案!

最近面试BAT,整理一份面试资料Java面试BATJ通关手册,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。

获取方式:点“在看”,关注公众号并回复 Java 领取,更多内容陆续奉上。

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

“在看”支持小哈呀,谢谢啦

继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存