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