Java Kotlin Lambda 的理解

513 阅读3分钟

1 Java 内部类 内存泄漏

public class JavaActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_java);

        View btn = findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                doWork();
            }
        });
    }

    void doWork() {
        Runnable work = new Runnable() {
            public void run() {
                try {
                    sleep(2000000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        new Thread(work).start();
    }

}

上面的代码,点击按钮后,关闭 Activity后,会发生内存泄漏(这不废话吗......) 看一下生成的文件,生成3个文件,2个内部类生成2个文件(也就是N个内部类生成N文件....)

0b0cc20d-4c39-4caf-9d52-3ad5ea6220e1.png

1cad3959-ccb4-4988-85c4-0ae9314fcb4b.png 通过生成的字节码我们可以明确看到以下结论

内部类会持有外部类的引用,通过构造方法传递 我们在通过字节码来确认一下吧,可以看到红色部分,持有外部类引用 image.png

总结

泄漏根本原因: 内部类持有外部类引用

解决:静态内部类,弱引用持有

2 Java Lambda

public class JavaLambdaActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_java_lambda);
        View btn = findViewById(R.id.btn);
        btn.setOnClickListener(v -> doWork());
    }

    void doWork() {
        Runnable work = () -> {
            try {
                sleep(2000000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        };
        new Thread(work).start();
    }
}

和上面的JavaActivity 完全一样,只不过写法变成lambda表示,会发生什么?

在看一下字节码

d46219d4-1131-4ae7-add8-9663d62a88bd.png

1 Java代码里面,使用Lambda表达式可以减少内部类文件的生成

2 而每个class文件,被加载到虚拟机后都会在方法去保留类信息,而class本身也有独立的常量池等

3 而在使用时在一个class查询快,还是多个class里面查询快?

结论:推荐使用Lambda替代之前写法

3 Kotlin 内部类 (与外部类无关联)

class KotlinActivity : BaseActivity() {

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_kotlin)

       val button = findViewById<View>(R.id.btn)
       button.setOnClickListener { doWork() }
   }

   private fun doWork() {
       val work = Runnable {
           sleep(2000000)
       }
       Thread(work).start()
   }
}

还是和和上面的JavaActivity 完全一样,只不过语言是kotlin了,那这样会内存泄漏吗?

答案:不会(因为此时内部类没有引用任何内部类信息)

  • app\build\tmp\kotlin-classes\debug 目录下 执行命令

和Java lambda 是不是很像(kotlin没有doWork 这部分字节码)

  • 那为什么kotlin 不会内存泄漏了?

image (1).png

image.png

通过上面smail,很容易得出结论

1 kotlin 内部类没有与外部类关联的时候,使用的是 invoke-static

2 所以 ,不会发生内存泄

4 Kotlin 内部类 (与外部类有关联)

class KotlinActivity : BaseActivity() {
    private var test = 0
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_kotlin)

        val button = findViewById<View>(R.id.btn)
        button.setOnClickListener { doWork() }
    }

    private fun doWork() {
        val work = Runnable {
            test = 110
            test()
            sleep(2000000)
        }
        Thread(work).start()
    }

    fun test() {
    }
}

fb2df5ae-e4dc-4d3a-8ea8-a59076945b78.png

结论

1 kotlin 内部类与外部类关联的时候,会发生内存泄了

2 看上面的smail文件,左侧有关联,右侧无关联

5 Java Lambda VS Kotlin

我们apk包中,发现Java Lambda写法和kotlin 内部类写法完全一样,都是生成2个文件

对比一下2个字节码

1.png

2.png

kotlin没有doWork 字节码

对比一下2个smail文件

11.png

onClick 方法的时候 Java是invoke-virtual 而kotlin invoke-static

22.png

这2个文件几乎完全一致

结论

Java Lambda 会发生内存泄泄漏吗??

只有Java Lambda显式调用外部类时才会强引用

原始文档 :qdp21w3pc9.feishu.cn/docs/doccnN…