最新分析內(nèi)存泄漏問(wèn)題的時(shí)候,發(fā)現(xiàn)引用鏈里有一個(gè)SynchronizedLazyImpl,搜了一下發(fā)現(xiàn)是by lazy相關(guān)的,而這個(gè)是實(shí)現(xiàn)單例懶加載的語(yǔ)法糖,所以猜測(cè)可能是這里引起的泄漏,于是研究了一下by lazy會(huì)不會(huì)引起泄漏。
創(chuàng)新互聯(lián)建站是一家專(zhuān)業(yè)提供象山企業(yè)網(wǎng)站建設(shè),專(zhuān)注與做網(wǎng)站、網(wǎng)站制作、H5響應(yīng)式網(wǎng)站、小程序制作等業(yè)務(wù)。10年已為象山眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專(zhuān)業(yè)網(wǎng)站設(shè)計(jì)公司優(yōu)惠進(jìn)行中。本篇文章會(huì)通過(guò)一個(gè)Demo來(lái)一探究竟。
一、by lazy原理 1、by lazy是干嘛的by lazy是懶加載,是實(shí)現(xiàn)單例的一個(gè)方法,這樣加載的變量會(huì)在第一次用到的時(shí)候才會(huì)進(jìn)行初始化。
2、探究by lazy的原理先寫(xiě)一個(gè)test的類(lèi)
class TestClass(context: Context) {init {Log.d("TestClass", "init()!")
}
}
然后在Activity里通過(guò)by lazy來(lái)初始化一個(gè)變量。
class TestActivity : AppCompatActivity() {val testClx by lazy {val context = this
TestClass(context)
}
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
}
}
想要一探by lazy的究竟,最好是通過(guò)字節(jié)碼,但是字節(jié)碼太難懂了,那就再將字節(jié)碼Decompile成.java文件。
方法:Tools->kotlin->Show Kotlin Bytecode
然后再點(diǎn)一下Decomile,就會(huì)生成.java文件了。
public final class TestActivity extends AppCompatActivity {@NotNull
private final Lazy testClx$delegate = LazyKt.lazy((Function0)(new Function0() { // $FF: synthetic method
// $FF: bridge method
public Object invoke() { return this.invoke();
}
@NotNull
public final TestClass invoke() { TestActivity context = TestActivity.this;
return new TestClass((Context)context);
}
}));
@NotNull
public final TestClass getTestClx() { Lazy var1 = this.testClx$delegate;
Object var3 = null;
return (TestClass)var1.getValue();
}
protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState);
this.setContentView(1300001);
}
}
從.java文件可以看到,TestActivity里并沒(méi)有testClx這個(gè)成員變量,而是testClx$delegate。
當(dāng)要使用testClx這個(gè)變量的時(shí)候,其實(shí)是通過(guò)getTestClx()這個(gè)方法暴露給了外界。而getTestClx()這個(gè)方法內(nèi)部,其實(shí)是通過(guò)testClx$delegate.getValue()方法來(lái)獲取值的。
那我們的分析重點(diǎn)就來(lái)到了testClx$delegate這個(gè)東西。這個(gè)東西在TestActivity創(chuàng)建的時(shí)候就進(jìn)行初始化了,我們進(jìn)入LazyKt.lazy方法看一下。
public actual funlazy(initializer: () ->T): Lazy= SynchronizedLazyImpl(initializer)
這里實(shí)際是走了SynchronizedLazyImpl,那我們繼續(xù)深入
private class SynchronizedLazyImpl(initializer: () ->T, lock: Any? = null) : Lazy, Serializable {private var initializer: (() ->T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get() {val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {@Suppress("UNCHECKED_CAST") (_v2 as T)
} else {val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
...
}
這個(gè)類(lèi)里其實(shí)也并不復(fù)雜,構(gòu)建函數(shù)接受一個(gè)lamda表達(dá)式,這里的表達(dá)式就是by lazy 代碼塊里的代碼。
內(nèi)部有一個(gè)value,就是外部testClx$delegate.getValue()這里獲取的,那重點(diǎn)就在get()里了。
然后就會(huì)發(fā)現(xiàn),內(nèi)部的實(shí)現(xiàn)完全就是一個(gè)Java式的雙重校驗(yàn)單例呀。
如果為value為null,會(huì)先鎖住,再進(jìn)行一次判斷,如果還未null,就進(jìn)行初始化,這里的初始化就是通過(guò)lamda表達(dá)式來(lái)進(jìn)行初始化。
然后進(jìn)行初始化之后,會(huì)把initializer置空,這一步是個(gè)重點(diǎn),我們后面再說(shuō)。
那到這里by lazy的原理我們也搞清楚了,利用double check來(lái)保證單例。
可以在TestActivity里去多次調(diào)用testClx試一下,TestClass里init的log只會(huì)打印一次,并且在第一次調(diào)用的時(shí)候才會(huì)打印。
二、會(huì)不會(huì)泄漏在探究之前,我先去網(wǎng)上搜索了一下相關(guān)的問(wèn)題。發(fā)現(xiàn)有好幾篇文章說(shuō)會(huì)泄漏,stack overflow上也有這樣的回答:
https://stackoverflow.com/questions/51718733/why-kotlin-by-lazy-can-cause-memory-leak-in-android
大致的意思就是,by lazy會(huì)持有l(wèi)ambda表達(dá)式中會(huì)持有context的引用,這里的引用一直到變量初始化之后才會(huì)被釋放,如果變量訪問(wèn)較晚或者沒(méi)有訪問(wèn)就可能會(huì)導(dǎo)致內(nèi)存泄漏。
這么一聽(tīng)好像還挺有道理的,于是準(zhǔn)備驗(yàn)證一下。
1、驗(yàn)證會(huì)不會(huì)泄漏我們從Main Activity跳轉(zhuǎn)到TestActivity,但是TestActivity里的testClx變量從未被訪問(wèn),也就不會(huì)初始化。
class MainActivity : AppCompatActivity() {lateinit var path: String
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val btn: Button = findViewById(R.id.btn_jump)
btn.setOnClickListener {val intent = Intent(this, TestActivity::class.java)
startActivity(intent)
}
val dumpBtn: Button = findViewById(R.id.btn_dump)
dumpBtn.setOnClickListener {dump()
}
path = externalCacheDir!!.path
}
fun dump() {Debug.dumpHprofData("$path/test.hprof")
}
}
跳轉(zhuǎn)過(guò)后回到MainActivity,并將hprof文件dump下來(lái)導(dǎo)入profiler查看。
此時(shí)的預(yù)期應(yīng)該是TestActivity會(huì)發(fā)生泄漏,但實(shí)際情況卻并沒(méi)有:
那就和文章里說(shuō)的不對(duì)了,我們回到.java文件里深究一下。
2、深究public final class TestActivity extends AppCompatActivity {@NotNull
private final Lazy testClx$delegate = LazyKt.lazy((Function0)(new Function0() { // $FF: synthetic method
// $FF: bridge method
public Object invoke() { return this.invoke();
}
@NotNull
public final TestClass invoke() { TestActivity context = TestActivity.this;
return new TestClass((Context)context);
}
}));
...
}
可以看到,這里L(fēng)azyKt.lazy方法里,寫(xiě)了一個(gè)匿名內(nèi)部類(lèi)Function0。
function0里的invoke()方法,就是我們進(jìn)行初始化的內(nèi)容??梢钥吹竭@個(gè)匿名內(nèi)部類(lèi)Function0是持有了TestAcivity的引用的。
如果按照前面的說(shuō)法會(huì)泄漏的話(huà),那初始化里將initializer置空就很重要,初始化之后會(huì)釋放掉
那這里會(huì)不會(huì)泄漏呢?我們畫(huà)個(gè)圖分析一下:
當(dāng)TestActivity在前臺(tái)的時(shí)候,肯定是不會(huì)被回收的,從GCRoot出發(fā)是可達(dá)的。
當(dāng)TestActivity銷(xiāo)毀之后,原本的引用鏈斷了
雖然Function0持有了TestActivity的實(shí)例,但是他們都是從GCRoot不可達(dá)的,當(dāng)發(fā)生GC時(shí)他們都是會(huì)被回收的。那都會(huì)被回收,從從哪里來(lái)的內(nèi)存泄漏呢?
所以結(jié)論就是,by lazy初始化的變量,是不會(huì)引起內(nèi)存泄漏的!
3、對(duì)比大家可能都聽(tīng)說(shuō)過(guò),Activity里的匿名內(nèi)部類(lèi)handler可能會(huì)造成內(nèi)存泄漏,和這里by lazy有什么不一樣呢?
我們就要明白handler泄漏的真正原因:
通過(guò)handler發(fā)送了一條message,此時(shí)的message是持有handler引用的。如果這條handler在消息隊(duì)列里沒(méi)有被發(fā)出,此時(shí)Activity銷(xiāo)毀了,那么就會(huì)存在這樣一跳引用鏈:
主線(xiàn)程 —>threadlocal —>Looper —>MessageQueue —>Message —>Handler —>Activity
這里是因?yàn)閠hreadlocal是常駐的,不會(huì)被回收,所以才導(dǎo)致了Activity不能被回收而泄漏。
**而我們前面的情況,并沒(méi)有這樣一條引用鏈。**所以,要搞清楚,并不是匿名內(nèi)部類(lèi)都會(huì)造成內(nèi)存泄漏!
在判斷有沒(méi)有內(nèi)存泄漏時(shí),我們還是要通過(guò)本質(zhì)去判斷,到底有沒(méi)有一條從GCRoot的引用鏈,導(dǎo)致已經(jīng)銷(xiāo)毀的類(lèi)無(wú)法被回收。
三、總結(jié)通過(guò)實(shí)踐、深究源碼、與handler泄漏對(duì)比,我們可以知道正常使用by lazy初始化的變量并不會(huì)導(dǎo)致內(nèi)存泄漏。
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購(gòu),新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧
網(wǎng)站欄目:kotlin的bylazy會(huì)不會(huì)導(dǎo)致內(nèi)存泄漏-創(chuàng)新互聯(lián)
分享URL:http://www.rwnh.cn/article28/cciojp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供營(yíng)銷(xiāo)型網(wǎng)站建設(shè)、小程序開(kāi)發(fā)、網(wǎng)站改版、建站公司、做網(wǎng)站、ChatGPT
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容