前些天,有人問到 “開發(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ū)域
這里以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原理解析
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