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

戳上方蓝字“Java面试题精选”关注!

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

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

接着我们查看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, "u6d4bu8bd5"

    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, "u6d4bu8bd5"

    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

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

打开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, "u6d4bu8bd5"

    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 表达式,若内部没有引用外部类,则不会持有外部类;若内部引用到了外部类,则会持有外部类。

感谢阅读,希望对你有所帮助 :)   来源:

blog.csdn.net/hbdatouerzi/article/details/122534134

后端专属技术群

构建高质量的技术交流社群,欢迎从事编程开发、技术招聘HR进群,也欢迎大家分享自己公司的内推信息,相互帮助,一起进步!

文明发言,以交流技术职位内推行业探讨为主

广告人士勿入,切勿轻信私聊,防止被骗

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

加我好友,拉你进群

原文始发于微信公众号(Java面试题精选):Lambda 表达式到底会不会造成内存泄漏?

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/196680.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!