Java內(nèi)存問題 及 LeakCanary 原理分析

前些天,有人問到 “開發(fā)過程中常見的內(nèi)存泄漏都有哪些上遥?”澡谭,一時(shí)脫口而出:靜態(tài)的對(duì)象中(包括單例)持有一個(gè)生命周期較短的引用時(shí),或內(nèi)部類的子代碼塊對(duì)象的生命周期超過了外面代碼的生命周期(如非靜態(tài)內(nèi)部類西剥,線程)痹栖,會(huì)導(dǎo)致這個(gè)短生命周期的對(duì)象內(nèi)存泄漏〔t空?傊褪且粋€(gè)對(duì)象的生命周期結(jié)束(不再使用該對(duì)象)后揪阿,依然被某些對(duì)象所持有該對(duì)象強(qiáng)引用的場(chǎng)景就是內(nèi)存泄漏。

這樣回答很明顯并不是問答人想要的都有哪些場(chǎng)景匙铡,所以這里抽時(shí)間整理了下內(nèi)存相關(guān)的知識(shí)點(diǎn)图甜,及LeakCanary工具的原理分析。

Java內(nèi)存問題 及 LeakCanary 原理分析

在安卓等其他移動(dòng)平臺(tái)上鳖眼,內(nèi)存問題顯得特別重要黑毅,想要做到虛擬機(jī)內(nèi)存的高效利用,及內(nèi)存問題的快速定位钦讳,了解下虛擬機(jī)內(nèi)存模塊及管理相關(guān)知識(shí)是很有必要的矿瘦,這篇文章將從最基礎(chǔ)的知識(shí)分析,內(nèi)存問題的產(chǎn)生地方愿卒、原因缚去、解決方案等原理。

一琼开、運(yùn)行時(shí)內(nèi)存區(qū)域

內(nèi)存區(qū)

這里以Java虛擬機(jī)為例易结,將運(yùn)行時(shí)內(nèi)存區(qū)分為不同的區(qū)域,每個(gè)區(qū)域承擔(dān)著不同的功能。

方法區(qū)
用戶存儲(chǔ)已被虛擬機(jī)加載的類信息搞动,常量躏精,靜態(tài)常量,即時(shí)編譯器編譯后的代碼等數(shù)據(jù)鹦肿。異常狀態(tài) OutOfMemoryError矗烛,其中包含常量池和用戶存放編譯器生成的各種字面量和符號(hào)引用。


是JVM所管理的內(nèi)存中最大的一塊箩溃。唯一目的就是存放實(shí)例對(duì)象瞭吃,幾乎所有的對(duì)象實(shí)例都在這里分配。Java堆是垃圾收集器管理的主要區(qū)域涣旨,因此很多時(shí)候也被稱為“GC堆”歪架。異常狀態(tài) OutOfMemoryError。

虛擬機(jī)棧
描述的是java方法執(zhí)行的內(nèi)存模型,每個(gè)方法在執(zhí)行時(shí)都會(huì)創(chuàng)建一個(gè)棧幀霹陡,用戶存儲(chǔ)局部變量表牡拇,操作數(shù)棧,動(dòng)態(tài)連接穆律,方法出口等信息惠呼。每一個(gè)方法從調(diào)用直至完成的過程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過程峦耘。 對(duì)這個(gè)區(qū)域定義了兩種異常狀態(tài) OutOfMemoryError剔蹋、StackOverflowError。

本地方法棧
虛擬機(jī)棧為虛擬機(jī)執(zhí)行java方法辅髓,而本地方法棧為虛擬機(jī)使用到的Native方法服務(wù)泣崩。異常狀態(tài)StackOverFlowError、OutOfMemoryError洛口。

程序計(jì)數(shù)器
一塊較小的內(nèi)存矫付,當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。字節(jié)碼解釋器工作時(shí)第焰,就是通過改變這個(gè)計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令买优。

內(nèi)存模型

Java內(nèi)存模型規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存中。每條線程中還有自己的工作內(nèi)存挺举,線程的工作內(nèi)存中保存了被該線程所使用到的變量杀赢,這些變量是從主內(nèi)存中拷貝而來。線程對(duì)變量的所有操作(讀湘纵,寫)都必須在工作內(nèi)存中進(jìn)行脂崔。不同線程之間也無法直接訪問對(duì)方工作內(nèi)存中的變量,線程間變量值的傳遞均需要通過主內(nèi)存來完成梧喷。

為了保證內(nèi)存可見性砌左,常常利用volatile關(guān)鍵子特性來保證變量的可見性(并不能保證并發(fā)時(shí)原子性)脖咐。

二、內(nèi)存如何回收

內(nèi)存的分配

一個(gè)對(duì)象從被創(chuàng)建到回收汇歹,主要經(jīng)歷階段有 1:創(chuàng)建階段(Created)文搂、2: 應(yīng)用階段(In Use)、3:不可見階段(Invisible)秤朗、4:不可達(dá)階段(Unreachable)、5:收集階段(Collected)笔喉、6:終結(jié)階段(取视、Finalized)、7:對(duì)象空間重分配階段(De-allocated)常挚。

內(nèi)存的分配實(shí)在創(chuàng)建階段作谭,這個(gè)階段要先用類加載器加載目標(biāo)class,當(dāng)通過加載器檢測(cè)后奄毡,就開始為新對(duì)象分配內(nèi)存折欠。對(duì)象分配內(nèi)存大小在類加載完成后便可以確定。
當(dāng)初始化完成后吼过,虛擬機(jī)還要對(duì)對(duì)象進(jìn)行必要的設(shè)置锐秦,如那個(gè)類的實(shí)例,如何查找元數(shù)據(jù)盗忱、對(duì)象的GC年代等酱床。

內(nèi)存的回收(GC)

那些不可能再被任何途徑使用的對(duì)象,需要被回收趟佃,否則內(nèi)存遲早都會(huì)被消耗空扇谣。

GC機(jī)制主要是通過可達(dá)性分析法,通過一系列稱為“GC Roots”的對(duì)象作為起始點(diǎn)闲昭,從這些節(jié)點(diǎn)向下搜索罐寨,搜索所走過的路徑稱為引用鏈,當(dāng)一個(gè)對(duì)象到GC Roots沒有任何引用鏈時(shí)序矩,即GC Roots到對(duì)象不可達(dá)鸯绿,則證明此對(duì)象是不可達(dá)的。

根據(jù)《深入理解Java虛擬機(jī)》書中描述簸淀,可作為GC Root的地方如下:

  • 虛擬機(jī)棧(棧幀中的局部變量區(qū)楞慈,也叫做局部變量表)中引用的對(duì)象。
  • 方法區(qū)中的類靜態(tài)屬性引用的對(duì)象啃擦。
  • 方法區(qū)中常量引用的對(duì)象囊蓝。
  • 本地方法棧中JNI(Native方法)引用的對(duì)象。

當(dāng)一個(gè)對(duì)象或幾個(gè)相互引用的對(duì)象組沒有任何引用鏈時(shí)令蛉,會(huì)被當(dāng)成垃圾處理聚霜,可以進(jìn)行回收狡恬。

如何一個(gè)對(duì)象在程序中已經(jīng)不再使用,但是(強(qiáng))引用還是會(huì)被其他對(duì)象持有蝎宇,則稱為內(nèi)存泄漏弟劲。內(nèi)存泄漏并不會(huì)使程序馬上異常,但是多處的未處理的內(nèi)存泄漏則可能導(dǎo)致內(nèi)存溢出姥芥,造成不可預(yù)估的后果兔乞。

引用的分類

在JDK1.2之后,為了優(yōu)化內(nèi)存的利用及GC的效率凉唐,Java對(duì)引用的概念進(jìn)行了擴(kuò)充庸追,將引用分為強(qiáng)引用、軟引用台囱、弱引用淡溯、虛引用4種。

1簿训、強(qiáng)引用咱娶,只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象强品。

2膘侮、軟引用,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前的榛,將會(huì)把這些對(duì)象列進(jìn)回收范圍進(jìn)行二次回收喻喳。如果這次回收還沒有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常困曙。SoftReference表示軟引用表伦。

3、弱引用慷丽,只要有GC蹦哼,無論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉被弱引用關(guān)聯(lián)的對(duì)象要糊。WeakReference表示弱引用纲熏。

4、虛引用锄俄,這個(gè)引用存在的唯一目的就是在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知局劲,被虛引用關(guān)聯(lián)的對(duì)象,和其生存時(shí)間完全沒關(guān)系奶赠。PhantomReference表示虛引用鱼填,需要搭配ReferenceQueue使用,檢測(cè)對(duì)象回收情況毅戈。

關(guān)于JVM內(nèi)存管理的一些建議

1苹丸、盡可能的手動(dòng)將無用對(duì)象置為null愤惰,加快內(nèi)存回收。
2赘理、可考慮對(duì)象池技術(shù)生成可重用的對(duì)象宦言,較少對(duì)象的生成。
3商模、合理利用四種引用奠旺。

三、內(nèi)存泄漏

持有一個(gè)生命周期較短的引用時(shí)或內(nèi)部的子模塊對(duì)象的生命周期超過了外面模塊的生命周期施流,即本該被回收的對(duì)象不能被回收而停留在堆內(nèi)存中响疚,這就產(chǎn)生了內(nèi)存泄漏。

內(nèi)存泄漏是造成應(yīng)用程序OOM的主要原因之一嫂沉,尤其在像安卓這樣的移動(dòng)平臺(tái),難免會(huì)導(dǎo)致應(yīng)用所需要的內(nèi)存超過系統(tǒng)分配的內(nèi)存限額扮碧,這就造成了內(nèi)存溢出Error趟章。

安卓平臺(tái)常見的內(nèi)存泄漏

1、靜態(tài)成員變量持有外部(短周期臨時(shí))對(duì)象引用慎王。 如單例類(類內(nèi)部靜態(tài)屬性)持有一個(gè)activity(或其他短周期對(duì)象)引用時(shí)蚓土,導(dǎo)致被持有的對(duì)象內(nèi)存無法釋放。

2赖淤、內(nèi)部類蜀漆。當(dāng)內(nèi)部類與外部類生命周期不一致時(shí),就會(huì)造成內(nèi)存泄漏咱旱。如非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實(shí)例确丢、Activity中的Handler或Thread等。

3吐限、資源沒有及時(shí)關(guān)閉鲜侥。如數(shù)據(jù)庫(kù)、IO流诸典、Bitmap描函、注冊(cè)的相關(guān)服務(wù)、webview狐粱、動(dòng)畫等舀寓。

4、集合內(nèi)部Item沒有置空肌蜻。

5互墓、方法塊內(nèi)不使用的對(duì)象,沒有及時(shí)置空蒋搜。

四轰豆、如何檢測(cè)內(nèi)存泄漏

Android Studio供了許多對(duì)App性能分析的工具胰伍,可以方便分析App性能。我們可以使用Memory Monitor和Heap Dump來觀察內(nèi)存的使用情況酸休、使用Allocation Tracker來跟蹤內(nèi)存分配的情況骂租,也可以通過這些工具來找到疑似發(fā)生內(nèi)存泄漏的位置。

堆存儲(chǔ)文件(hpof)可以使用DDMS或者M(jìn)emory Monitor來生成斑司,輸出的文件格式為hpof渗饮,而MAT(Memory Analysis Tool)就是來分析堆存儲(chǔ)文件的。

然而MAT工具分析內(nèi)存問題并不是一件容易的事情宿刮,需要一定的經(jīng)驗(yàn)區(qū)做引用鏈的分析互站,需要一定的門檻。
隨著安卓技術(shù)生態(tài)的發(fā)展僵缺,LeakCanary 開源項(xiàng)目誕生了胡桃,只要幾行代碼引入目標(biāo)項(xiàng)目,就可以自動(dòng)分析hpof文件磕潮,把內(nèi)存泄漏的地方展示出來翠胰。

五、LeakCanary原理解析

LeakCanary

A small leak will sink a great ship.

LeakCanary內(nèi)存檢測(cè)工具是由squar公司開源的著名項(xiàng)目自脯,這里主要分析下源碼實(shí)現(xiàn)原理之景。

基本原理

主要是在Activity的&onDestroy方法中,手動(dòng)調(diào)用 GC膏潮,然后利用ReferenceQueue+WeakReference锻狗,來判斷是否有釋放不掉的引用,然后結(jié)合dump memory的hpof文件, 用HaHa分析出泄漏地方焕参。

源碼分析

LeakCanary集成很方便轻纪,只要幾行代碼,所以可以從入口跟蹤代碼叠纷,分析原理

                if (!LeakCanary.isInAnalyzerProcess(WeiboApplication.this)) {
                    LeakCanary.install(WeiboApplication.this);
                }
                
                public static RefWatcher install(Application application) {
                      return ((AndroidRefWatcherBuilder)refWatcher(application)
                      .listenerServiceClass(DisplayLeakService.class).excludedRefs(AndroidExcludedRefs.createAppDefaults().build()))//配置監(jiān)聽器及分析數(shù)據(jù)格式
                      .buildAndInstall();
               }

從這里可看出桐磁,LeakCanary會(huì)單獨(dú)開一進(jìn)程,用來執(zhí)行分析任務(wù)讲岁,和監(jiān)聽任務(wù)分開處理我擂。

方法install中主要是構(gòu)造來一個(gè)RefWatcher,

   public RefWatcher buildAndInstall() {
        RefWatcher refWatcher = this.build();
        if(refWatcher != RefWatcher.DISABLED) {
            LeakCanary.enableDisplayLeakActivity(this.context);
            ActivityRefWatcher.install((Application)this.context, refWatcher);
        }

        return refWatcher;
    }
    
    public static void install(Application application, RefWatcher refWatcher) {
        (new ActivityRefWatcher(application, refWatcher)).watchActivities();
    }
    
    private final ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacks() {
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        }
        public void onActivityStarted(Activity activity) {}
        public void onActivityResumed(Activity activity) {}
        public void onActivityPaused(Activity activity) {}
        public void onActivityStopped(Activity activity) { }
        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}

        public void onActivityDestroyed(Activity activity) {
            ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
    };
    
    void onActivityDestroyed(Activity activity) {
        this.refWatcher.watch(activity);
    }

具體監(jiān)聽的原理在于 Application 的registerActivityLifecycleCallbacks方法,該方法可以對(duì)應(yīng)用內(nèi)所有 Activity 的生命周期做監(jiān)聽, LeakCanary只監(jiān)聽了Destroy方法。

在每個(gè)Activity的OnDestroy()方法中都會(huì)回調(diào)refWatcher.watch()方法败晴,那我們找到的RefWatcher的實(shí)現(xiàn)類,看看具體做什么衙吩。

 public void watch(Object watchedReference, String referenceName) {
        if(this != DISABLED) {
            Preconditions.checkNotNull(watchedReference, "watchedReference");
            Preconditions.checkNotNull(referenceName, "referenceName");
            long watchStartNanoTime = System.nanoTime();
            String key = UUID.randomUUID().toString();//保證key的唯一性
            this.retainedKeys.add(key);
            KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, this.queue);
            this.ensureGoneAsync(watchStartNanoTime, reference);
        }
    }
    
    
  final class KeyedWeakReference extends WeakReference<Object> {
    public final String key;
    public final String name;

    KeyedWeakReference(Object referent, String key, String name, ReferenceQueue<Object> referenceQueue) {//ReferenceQueue類監(jiān)聽回收情況
        super(Preconditions.checkNotNull(referent, "referent"), (ReferenceQueue)Preconditions.checkNotNull(referenceQueue, "referenceQueue"));
        this.key = (String)Preconditions.checkNotNull(key, "key");
        this.name = (String)Preconditions.checkNotNull(name, "name");
    }
  }

    private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
        this.watchExecutor.execute(new Retryable() {
            public Result run() {
                return RefWatcher.this.ensureGone(reference, watchStartNanoTime);
            }
        });
    }

KeyedWeakReference是WeakReference類的子類,用了 KeyedWeakReference(referent, key, name, ReferenceQueue<Object> )的構(gòu)造方法溪窒,將監(jiān)聽的對(duì)象(activity)引用傳遞進(jìn)來坤塞,并且New出一個(gè)ReferenceQueue來監(jiān)聽GC后 的回收情況冯勉。

以下代碼ensureGone()方法就是LeakCanary進(jìn)行檢測(cè)回收的核心代碼:

    Result ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
        long gcStartNanoTime = System.nanoTime();
        long watchDurationMs = TimeUnit.NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
        this.removeWeaklyReachableReferences();//先將引用嘗試從隊(duì)列中poll出來
        if(this.debuggerControl.isDebuggerAttached()) {//規(guī)避調(diào)試模式
            return Result.RETRY;
        } else if(this.gone(reference)) {//檢測(cè)是否已經(jīng)回收
            return Result.DONE;
        } else {
        //如果沒有被回收,則手動(dòng)GC
            this.gcTrigger.runGc();//手動(dòng)GC方法
            this.removeWeaklyReachableReferences();//再次嘗試poll摹芙,檢測(cè)是否被回收
            if(!this.gone(reference)) {
                // 還沒有被回收灼狰,則dump堆信息,調(diào)起分析進(jìn)程進(jìn)行分析
                long startDumpHeap = System.nanoTime();
                long gcDurationMs = TimeUnit.NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
                File heapDumpFile = this.heapDumper.dumpHeap();
                if(heapDumpFile == HeapDumper.RETRY_LATER) {
                    return Result.RETRY;//需要重試
                }

                long heapDumpDurationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
                this.heapdumpListener.analyze(new HeapDump(heapDumpFile, reference.key, reference.name, this.excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs));
            }

            return Result.DONE;
        }
    }

    private boolean gone(KeyedWeakReference reference) {
        return !this.retainedKeys.contains(reference.key);
    }

    private void removeWeaklyReachableReferences() {
        KeyedWeakReference ref;
        while((ref = (KeyedWeakReference)this.queue.poll()) != null) {
            this.retainedKeys.remove(ref.key);
        }
    }

方法ensureGone中通過檢測(cè)referenceQueue隊(duì)列中的引用情況浮禾,來判斷回收情況交胚,通過手動(dòng)GC來進(jìn)一步確認(rèn)回收情況。
整個(gè)過程肯定是個(gè)耗時(shí)卡UI的盈电,整個(gè)過程會(huì)在WatchExecutor中執(zhí)行的蝴簇,那WatchExecutor又是在哪里執(zhí)行的呢?

LeakCanary已經(jīng)利用Looper機(jī)制做了一定優(yōu)化匆帚,利用主線程空閑的時(shí)候執(zhí)行檢測(cè)任務(wù)熬词,這里找到WatchExecutor的實(shí)現(xiàn)類,研究下原理:

public final class AndroidWatchExecutor implements WatchExecutor {
    static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";
    private final Handler mainHandler = new Handler(Looper.getMainLooper());
    private final Handler backgroundHandler;
    private final long initialDelayMillis;
    private final long maxBackoffFactor;

    public AndroidWatchExecutor(long initialDelayMillis) {
        HandlerThread handlerThread = new HandlerThread("LeakCanary-Heap-Dump");
        handlerThread.start();
        this.backgroundHandler = new Handler(handlerThread.getLooper());
        this.initialDelayMillis = initialDelayMillis;
        this.maxBackoffFactor = 9223372036854775807L / initialDelayMillis;
    }

    public void execute(Retryable retryable) {
        if(Looper.getMainLooper().getThread() == Thread.currentThread()) {
            this.waitForIdle(retryable, 0);//需要在主線程中檢測(cè)
        } else {
            this.postWaitForIdle(retryable, 0);//post到主線程
        }

    }

    void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
        this.mainHandler.post(new Runnable() {
            public void run() {
                AndroidWatchExecutor.this.waitForIdle(retryable, failedAttempts);
            }
        });
    }

    void waitForIdle(final Retryable retryable, final int failedAttempts) {
        Looper.myQueue().addIdleHandler(new IdleHandler() {
            public boolean queueIdle() {
                AndroidWatchExecutor.this.postToBackgroundWithDelay(retryable, failedAttempts);//切換到子線程
                return false;
            }
        });
    }

    void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
        long exponentialBackoffFactor = (long)Math.min(Math.pow(2.0D, (double)failedAttempts), (double)this.maxBackoffFactor);
        long delayMillis = this.initialDelayMillis * exponentialBackoffFactor;
        this.backgroundHandler.postDelayed(new Runnable() {
            public void run() {
                Result result = retryable.run();//RefWatcher.this.ensureGone(reference, watchStartNanoTime)執(zhí)行
                if(result == Result.RETRY) {
                    AndroidWatchExecutor.this.postWaitForIdle(retryable, failedAttempts + 1);
                }

            }
        }, delayMillis);
    }
}

這里用到了Handler相關(guān)知識(shí)吸重,Looper中的MessageQueue有個(gè)mIdleHandlers隊(duì)列互拾,在獲取下個(gè)要執(zhí)行的Message時(shí),如果沒有發(fā)現(xiàn)可執(zhí)行的下個(gè)Msg晤锹,就會(huì)回調(diào)queueIdle()方法摩幔。

    Message next() {
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
                ···
                ···//省略部分消息查找代碼
                
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        ···
                        
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                
                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {//返回false彤委,則從隊(duì)列移除鞭铆,下次空閑不會(huì)調(diào)用。
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

其中的MessageQueue中加入一個(gè)IdleHandler焦影,當(dāng)線程空閑時(shí)车遂,就會(huì)去調(diào)用queueIdle()函數(shù),如果返回值為True斯辰,那么后續(xù)空閑時(shí)會(huì)繼續(xù)的調(diào)用此函數(shù)舶担,否則不再調(diào)用;

知識(shí)點(diǎn)

1彬呻,用ActivityLifecycleCallbacks接口來檢測(cè)Activity生命周期
2衣陶,WeakReference + ReferenceQueue 來監(jiān)聽對(duì)象回收情況
3,Apolication中可通過processName判斷是否是任務(wù)執(zhí)行進(jìn)程
4闸氮,MessageQueue中加入一個(gè)IdleHandler來得到主線程空閑回調(diào)
5剪况,LeakCanary檢測(cè)只針對(duì)Activiy里的相關(guān)對(duì)象蒲跨。其他類無法使用译断,還得用MAT原始方法

六、總結(jié)

內(nèi)存相關(guān)的問題基本問題回顧了下或悲,發(fā)現(xiàn)技術(shù)細(xì)節(jié)越扒越多孙咪。想要得到技術(shù)的提高堪唐,對(duì)這些技術(shù)細(xì)節(jié)的掌握是必要的,只有長(zhǎng)時(shí)間的積累扎實(shí)的技術(shù)細(xì)節(jié)基礎(chǔ)翎蹈,才能讓自己的技術(shù)走的更高淮菠。

基礎(chǔ)知識(shí)對(duì)每個(gè)工程師發(fā)展的不同階段意義不同,理解的角度和深度也不同杨蛋。至少自己來看兜材,基礎(chǔ)知識(shí)是永遠(yuǎn)值得學(xué)習(xí)和鞏固,來支撐技術(shù)的創(chuàng)新實(shí)踐逞力。


歡迎轉(zhuǎn)載曙寡,請(qǐng)標(biāo)明出處:常興E站 canking.win

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市寇荧,隨后出現(xiàn)的幾起案子举庶,更是在濱河造成了極大的恐慌,老刑警劉巖揩抡,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件户侥,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡峦嗤,警方通過查閱死者的電腦和手機(jī)蕊唐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烁设,“玉大人替梨,你說我怎么就攤上這事∽昂冢” “怎么了副瀑?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)恋谭。 經(jīng)常有香客問我糠睡,道長(zhǎng),這世上最難降的妖魔是什么疚颊? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任狈孔,我火速辦了婚禮,結(jié)果婚禮上材义,老公的妹妹穿的比我還像新娘均抽。我一直安慰自己,他們只是感情好母截,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布到忽。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪喘漏。 梳的紋絲不亂的頭發(fā)上护蝶,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音翩迈,去河邊找鬼持灰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛负饲,可吹牛的內(nèi)容都是我干的堤魁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼返十,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼妥泉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起洞坑,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤盲链,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后迟杂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刽沾,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年排拷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了侧漓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡监氢,死狀恐怖布蔗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情忙菠,我是刑警寧澤何鸡,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布纺弊,位于F島的核電站牛欢,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏淆游。R本人自食惡果不足惜傍睹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望犹菱。 院中可真熱鬧拾稳,春花似錦、人聲如沸腊脱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至悍抑,卻和暖如春鳄炉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背搜骡。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工拂盯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人记靡。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓谈竿,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親摸吠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子空凸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容