成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

kotlin的bylazy會不會導致內(nèi)存泄漏-創(chuàng)新互聯(lián)

最新分析內(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)

成都做網(wǎng)站
富锦市| 泾源县| 竹山县| 区。| 尚义县| 桐梓县| 安塞县| 肇州县| 吉安市| 逊克县| 巴林右旗| 宁海县| 安丘市| 海南省| 泸溪县| 东平县| 肃北| 咸阳市| 增城市| 绥滨县| 荃湾区| 长沙县| 肥城市| 扎赉特旗| 聂拉木县| 聊城市| 宜川县| 隆化县| 临朐县| 堆龙德庆县| 元谋县| 花莲市| 肥西县| 荆门市| 龙里县| 略阳县| 龙胜| 安新县| 朔州市| 依兰县| 和龙市|