1

从编译角度看Kotlin内存优化

 1 year ago
source link: https://www.51cto.com/article/714118.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

从编译角度看Kotlin内存优化-51CTO.COM

f864bf2c9cd3d0c07331b6a5dbd1f30e.png
从编译角度看Kotlin内存优化
作者:移动Labs 2022-07-15 13:01:13
今天我们来聊一聊由JetBrains开发的一种用于现代多平台应用的静态编程语言——Kotlin。

作者|闫永俊,单位:中国移动智慧家庭运营中心

​Labs 导读

今天我们来聊一聊由JetBrains开发的一种用于现代多平台应用的静态编程语言——Kotlin。

Kotlin可以被编译为Java字节码,也可以被编译成JavaScript,方便在没有JVM的设备上运行。除此之外,Kotlin还可以被编译成二进制代码直接运行在机器上。

在Google I/O2017中,Google宣布在Android上为Kotlin提供一等支持。目前,Kotlin已经成为Android应用开发的首选语言。

Kotlin相对于Java来说,有很多优点,如空安全、更加易用的Lambda表达式、支持扩展、众多的语法糖等。但是较少有人提及Kotlin的从编译角度上对Java做的内存优化,这里我们通过反编译的方法略窥一二。

Part 01  Java内部类持有外部类的引用

Java中有一个普遍的认知,Java中内部类会持有外部类的引用,使用不当就容易造成内存泄漏。参看下面例子。

我们编写如下代码来验证。

我们先创建一个父类,用于观察子类是否会调用finalize方法。​

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

我们创建一个子类,子类中创建一个不会终止的Thread。​

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();
 }
}

通过测试会发现,BaseActivity的finalize方法始终无法被调用,即外部类TmpJavaActivity始终无法被回收。我们使用反编译工具jadx来观察下反编译后的smail代码。

通过上述反编译后的smail代码可以看到,Java会将外部类对象作为参数传递给内部类对象,一旦内部类无法释放,会造成外部类一直无法被释放。从而造成内存泄漏。

Part 02  kotlin内部类非必要不持有外部类引用

上述同样的代码,我们使用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可以被正常回收。下面直接看下smail源码。

图片

从上述smail源码可以看到,与Java语言不同,Kotlin中的内部类会被编译成一个普通的类。因为内部类实际运行不依赖外部类,所以编译后,不会将外部类作为内部类构造方法的参数传递给内部类,即该内部类不会持有外部类的应用,所以不会造成内存泄漏。

但是如果内部类实际需要持有外部类引用呢?我们来观察如下代码​

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()
    }
}

观察反编译后的smail源码如下

图片

通过上述源码可以看到,在构造内部类的对象时,会将外部类的引用传递给内部类,从而造成内存泄漏。

通过上述两个例子可以看到。Kotlin语言中,内部类非必要不会持有外部类的引用,较Java而言,减少了内存泄漏的场景。​


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK