背景匿名内部类 VS Lambda 表达式
匿名内部类Lambda 表达式 结论
背景匿名内部类会持有外部类的引用,因此有造成内存泄漏的风险;
那么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;->(Lcom/example/jnihelper/TestInner;)V invoke-direct {v0, v1}, Ljava/lang/Thread;-> (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(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;-> ()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表达式的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;->(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()V .registers 1 new-instance v0, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0; invoke-direct {v0}, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;-> ()V sput-object v0, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;->INSTANCE:Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0; return-void .end method .method private synthetic constructor ()V .registers 1 invoke-direct {p0}, Ljava/lang/Object;-> ()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;->(Lcom/example/jnihelper/TestInner;)V invoke-direct {v0, v1}, Ljava/lang/Thread;-> (Ljava/lang/Runnable;)V .line 13 invoke-virtual {v0}, Ljava/lang/Thread;->start()V .line 14 return-void .end method
我们发现对应的实现方法lambda$test 0 0 0com-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(Lcom/example/jnihelper/TestInner;)V .registers 2 invoke-direct {p0}, Ljava/lang/Object;-> ()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 表达式中需显示的调用外部类,才会持有外部类的引用。
结论匿名内部类会持有外部类的引用,有造成内存泄漏的风险;
Lambda 表达式,若内部没有引用外部类,则不会持有外部类;若内部引用到了外部类,则会持有外部类。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)