👉 欢迎 ,你将获得: 专属的项目实战 / 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:因公众号平台更改了推送规则,如果不想错过内容,记得读完点一下「在看」,加个「星标」,这样每次新文章推送才会第一时间出现在你的订阅列表里。
点「在看」支持小哈呀,谢谢啦