栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

匿名内部类持有父类的引用会导致内存泄漏?Kotlin:没有那么简单

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

匿名内部类持有父类的引用会导致内存泄漏?Kotlin:没有那么简单

1. 结论

做Android的,或者做Java开发的同学,通常都会听到一句话,内部类持有外部类的引用,可能会导致外部类内存泄漏。果真是这样吗?

知道大家很忙,那就先把结论抛出来

  • Java中内部类会持有外部类的引用,可能引发内存泄漏
  • Kotlin存在编译优化,会将内部类编译成普通的类,如果内部类中没有实际引用外部类,则不会造成内存泄漏;如果内部类中引用了外部类,则会将外部类的作为参数传递给“内部类”,进而可能引发内存泄漏。
  • Kotlin协程中,GlobalScope与上条相同,如果协程内部没有实际引用外部类,则不会造成内存泄漏;如果实际引用了外部类,则可能造成内存泄漏;
  • Kotlin协程中,lifecycleScope等会在onDestroy前cancel掉挂起状态协程的执行,所以在上条的基础上,会进一步缩小内存泄漏的场景。
  • 综上,Kotlin较Java而言,优化了编译,减少了内存泄漏的场景;如果使用协程,尽量避免使用GlobalScope。

具体的实践过程如下

2. Java中匿名内部类持有父类的引用

首先我们要定义一个baseActivity,重写他的finalize,让我们更方便的看到Activity的回收过程

public class baseActivity extends AppCompatActivity {
    @Override
    protected void finalize() throws Throwable {
        Log.e("yanlog", "baseActivity finalize:" + this);
        super.finalize();
    }
}

我们用Android Studio的Profile来发起回收

先来一个最普普通通的用法

public class TmpJavaActivity extends baseActivity{
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread.start();
    }
}

我们经过测试会发现,TmpJavaActivity始终无法被回收。
参看下编译后的smail文件

从上面的编译结果来看,TmpJavaActivity确实作为一个内部类持有TmpJavaActivity的应用所以我们有了以下结论

Java中匿名内部类确实会持有父类的应用,进而有可能导致内存泄漏

3. Kotlin中匿名内部类持有外部类的引用

将2中的代码翻译为Kotlin,如下

class TmpActivity : baseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_tmp)
        val thread = Thread {
            while (true) {
                Thread.sleep(1000)
            }
        }
        thread.start()
    }
}

然后经过测试,**我们竟然神奇的发现TmpActivity被回收了!**有如下的日志打印

11-02 20:06:29.469 13127 13142 E yanlog : baseActivity finalize:com.cmri.myapplication.TmpActivity@b9d9408

我们惯例反编译apk看下原因

这里,编译生成了一个TmpActivity$$ExternalSyntheticLambda0类,这个类是一个有一个静态对象,该静态对象不持有Activity的引用,所以导致没有内存泄漏。

那么如果我们在run方法中引用外部类呢? 难道也不持有引用吗?

改下代码看看

class TmpActivity : baseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_tmp)
        val thread = Thread {
            while (true) {
                Log.e("yanlog","thread"+this@TmpActivity)
                java.lang.Thread.sleep(1000)
            }
        }
        thread.start()
    }
}


可以很清楚的看到,如果内部类中确实引用外部类的情况下,会将外部类作为参数传入到外面。相当于”内部类“持有外部类的引用。
综上,我们得到以下结论

1. kotlin编译后,会将内部类编译为普通的类A,一般情况下不持有外部类的引用。
2. 如果内部类代码中确实引用了外部类,那么编译后,会将外部类作为参数传入到类A中,相当于”内部类“持有外部类的应用。
3. Kotlin相对于Java类来说,内部类除非必要,否则不持有外部类的引用,所以会减少内存泄漏的场景。

4. 协程中使用GlobalScope会内存泄漏吗?

也要分两种情况,协程中不引用外部类

class TmpActivity : baseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_tmp)

        GlobalScope.launch {
            while (true) {
                delay(1000)
            }
        }
    }
}

结果

11-03 09:21:00.490 21426 21441 E yanlog : baseActivity finalize:com.cmri.myapplication.TmpActivity@d229f01

可以看到不会造成内存泄漏

但是如果协程中确实引用了外部类,如下

class TmpActivity : baseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_tmp)

        GlobalScope.launch {
            while (true) {
                Log.e("yanlog","TempActivity:"+this@TmpActivity)
                delay(1000)
            }
        }
    }
}

会发现TmpActivity一直无法被回收,看下反编译后的结果

可以看到TmpActivity被作为参数传递了其他类,所以会造成内存泄漏。

所以可以得出结论

1. kotlin协程中,如果没有引用外部类,则不会造成内存泄漏
2. kotlin协程中,如果确实引用了外部类,则可能会造成内存泄漏

5. kotlin协程中的lifecycleScope会造成内存泄漏吗?

如果我们把上述代码中的GlobalScope改成lifecycleScope,那还会造成内存泄漏吗?

class TmpActivity : baseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_tmp)

        lifecycleScope.launch {
            while (true) {
                Log.e("yanlog","TempActivity:"+this@TmpActivity)
                delay(1000)
            }
        }
    }

    override fun onDestroy() {
        Log.e("yanlog","onDestory")
        super.onDestroy()
    }
}

答案是不会,并且,会在onDestory执行前,cancel掉协程的执行,看日志输出

11-03 09:35:31.587 23749 23910 E yanlog : TempActivity:com.cmri.myapplication.TmpActivity@d229f01
11-03 09:35:32.589 23749 23910 E yanlog : TempActivity:com.cmri.myapplication.TmpActivity@d229f01
11-03 09:35:33.591 23749 23910 E yanlog : TempActivity:com.cmri.myapplication.TmpActivity@d229f01
11-03 09:35:34.595 23749 23910 E yanlog : TempActivity:com.cmri.myapplication.TmpActivity@d229f01
11-03 09:35:35.598 23749 23910 E yanlog : TempActivity:com.cmri.myapplication.TmpActivity@d229f01
11-03 09:36:06.277 26053 26053 E yanlog : TempActivity:com.cmri.myapplication.TmpActivity@d229f01
11-03 09:36:07.294 26053 26053 E yanlog : TempActivity:com.cmri.myapplication.TmpActivity@d229f01
11-03 09:36:08.296 26053 26053 E yanlog : TempActivity:com.cmri.myapplication.TmpActivity@d229f01
11-03 09:36:08.499 26053 26053 E yanlog : onDestory
11-03 09:36:09.927 26053 26069 E yanlog : baseActivity finalize:com.cmri.myapplication.TmpActivity@d229f01

有人说我不走寻常路,用Thread.sleep代替delay后,会造成内存泄漏吗?

class TmpActivity : baseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_tmp)

        lifecycleScope.launch(Dispatchers.IO) {
            while (true) {
                Log.e("yanlog","TempActivity:"+this@TmpActivity)
                Thread.sleep(1000)
            }
        }
    }

    override fun onDestroy() {
        Log.e("yanlog","onDestory")
        super.onDestroy()
    }
}

答案是会造成内存泄漏的。原因也是比较显而易见的,协程一直处于一个运行状态,没有挂起状态,导致没有一个“时机”去取消协程的执行。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/435128.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号