從編譯角度看Kotlin內(nèi)存優(yōu)化
作者|閆永俊,單位:中國移動(dòng)智慧家庭運(yùn)營中心
?Labs 導(dǎo)讀
今天我們來聊一聊由JetBrains開發(fā)的一種用于現(xiàn)代多平臺應(yīng)用的靜態(tài)編程語言——Kotlin。
Kotlin可以被編譯為Java字節(jié)碼,也可以被編譯成JavaScript,方便在沒有JVM的設(shè)備上運(yùn)行。除此之外,Kotlin還可以被編譯成二進(jìn)制代碼直接運(yùn)行在機(jī)器上。
在Google I/O2017中,Google宣布在Android上為Kotlin提供一等支持。目前,Kotlin已經(jīng)成為Android應(yīng)用開發(fā)的首選語言。
Kotlin相對于Java來說,有很多優(yōu)點(diǎn),如空安全、更加易用的Lambda表達(dá)式、支持?jǐn)U展、眾多的語法糖等。但是較少有人提及Kotlin的從編譯角度上對Java做的內(nèi)存優(yōu)化,這里我們通過反編譯的方法略窺一二。
Part 01 Java內(nèi)部類持有外部類的引用
Java中有一個(gè)普遍的認(rèn)知,Java中內(nèi)部類會(huì)持有外部類的引用,使用不當(dāng)就容易造成內(nèi)存泄漏。參看下面例子。
我們編寫如下代碼來驗(yàn)證。
我們先創(chuàng)建一個(gè)父類,用于觀察子類是否會(huì)調(diào)用finalize方法。?
public class BaseActivity extends AppCompatActivity {
@Override
protected void finalize() throws Throwable {
Log.e("yanlog", "BaseActivity finalize:" + this);
super.finalize();
}
}我們創(chuàng)建一個(gè)子類,子類中創(chuàng)建一個(gè)不會(huì)終止的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();
}
}通過測試會(huì)發(fā)現(xiàn),BaseActivity的finalize方法始終無法被調(diào)用,即外部類TmpJavaActivity始終無法被回收。我們使用反編譯工具jadx來觀察下反編譯后的smail代碼。
通過上述反編譯后的smail代碼可以看到,Java會(huì)將外部類對象作為參數(shù)傳遞給內(nèi)部類對象,一旦內(nèi)部類無法釋放,會(huì)造成外部類一直無法被釋放。從而造成內(nèi)存泄漏。
Part 02 kotlin內(nèi)部類非必要不持有外部類引用
上述同樣的代碼,我們使用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()
}
}我們通過觀察日志發(fā)現(xiàn),外部類TmpActivity可以被正?;厥铡O旅嬷苯涌聪聅mail源碼。

從上述smail源碼可以看到,與Java語言不同,Kotlin中的內(nèi)部類會(huì)被編譯成一個(gè)普通的類。因?yàn)閮?nèi)部類實(shí)際運(yùn)行不依賴外部類,所以編譯后,不會(huì)將外部類作為內(nèi)部類構(gòu)造方法的參數(shù)傳遞給內(nèi)部類,即該內(nèi)部類不會(huì)持有外部類的應(yīng)用,所以不會(huì)造成內(nèi)存泄漏。
但是如果內(nèi)部類實(shí)際需要持有外部類引用呢?我們來觀察如下代碼?
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源碼如下

通過上述源碼可以看到,在構(gòu)造內(nèi)部類的對象時(shí),會(huì)將外部類的引用傳遞給內(nèi)部類,從而造成內(nèi)存泄漏。
通過上述兩個(gè)例子可以看到。Kotlin語言中,內(nèi)部類非必要不會(huì)持有外部類的引用,較Java而言,減少了內(nèi)存泄漏的場景。?

























