戳上方蓝字“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

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

打开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进群,也欢迎大家分享自己公司的内推信息,相互帮助,一起进步!
文明发言,以
交流技术
、职位内推
、行业探讨
为主广告人士勿入,切勿轻信私聊,防止被骗
加我好友,拉你进群
原文始发于微信公众号(Java面试题精选):Lambda 表达式到底会不会造成内存泄漏?
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/196680.html