本文從如下幾個問題着手,分析內存泄漏的問題,以及檢測內存泄漏的leakcanary的相關源碼解析。html
首先探討第一個問題:java
一句話歸納就是:沒有用的對象沒法回收的現象就是內存泄露node
android 系統爲每一個應用分配的內存是有限的,當一個對象已經不須要再使用了,本該被回收時,而有另一個正在使用的對象持有它的引用從而致使它不能被回收,這致使本該被回收的對象不能被回收而停留在堆內存中,這就產生了內存泄漏android
堆內存引伸:JVM內存模型算法
主要詳解:數組
1.1 虛擬機棧要點:緩存
1.2 Java堆要點:安全
1.3 方法區:性能優化
方法區主要存放的是已被虛擬機加載的類信息、常量、靜態變量、編譯器編譯後的代碼等數據。GC在該區域出現的比較少bash
1.4 運行時常量池:
運行時常量池也是方法區的一部分,用於存放編譯器生成的各類字面量和符號引用。
講到回收對象那麼就引出了第二個問題:
前面說過堆內存的特色是「進程獨立,線程共享」。換句話說,每一個JVM實例都擁有它們本身的Heap空間,從而保證了程序間的安全性,不過須要注意的是進程內部的各個線程會共享一個堆空間的狀況下的代碼同步問題。
JAVA相較其餘語言的一個重要區別就是它具有垃圾自動回收功能——這同時也是堆內存管理系統最關鍵的一個功能。隨着JVM的不斷更新換代,其所支持的垃圾回收算法也在不停地推陳出新。這裏就簡述下最流行的算法之一,即「分代垃圾回收」算法
簡而言之,分代回收機制會將內存劃分爲以下「三代」來區別管理:
工做流程:
JVM對於不一樣代中的內存鎖採用的垃圾回收算法也是有區別的。
垃圾回收機制是由垃圾收集器Garbage Collection GC來實現的,GC是後臺的守護進程。它的特別之處是它是一個低優先級進程,可是能夠根據內存的使用狀況動態的調整他的優先級。所以,它是在內存中低到必定限度時纔會自動運行,從而實現對內存的回收。這就是垃圾回收的時間不肯定的緣由。
爲什麼要這樣設計:由於GC也是進程,也要消耗CPU等資源,若是GC執行過於頻繁會對java的程序的執行產生較大的影響(java解釋器原本就不快),所以JVM的設計者們選着了不按期的gc。
每一個應用程序都包含一組根(root)。每一個根都是一個存儲位置,其中包含指向引用類型對象的一個指針。該指針要麼引用託管堆中的一個對象,要麼爲null。
在應用程序中,只要某對象變得不可達,也就是沒有根(root)引用該對象,這個對象就會成爲垃圾回收器的目標。
接下來看看哪些操做會形成內存泄漏,以及處理辦法:
能夠簡單的作以下歸類:
例子
好比咱們有一個叫作AppSettings的類,它是一個單例模式
public class AppSettings {
private Context mAppContext;
private static AppSettings sInstance = new AppSettings();
//some other codes
public static AppSettings getInstance() {
return sInstance;
}
public final void setup(Context context) {
mAppContext = context;
}
}
複製代碼
當咱們傳入Activity做爲Context參數時,AppSettings實例會持有這個Activity的實例。 又當咱們旋轉設備時,Android系統會銷燬當前的Activity,建立新的Activity來加載合適的佈局。若是出現Activity被單例實例持有,那麼旋轉過程當中的舊Activity沒法被銷燬掉。就發生了內存泄漏。
解決辦法: 那就是使用Application的Context對象,由於它和AppSettings實例具備相同的生命週期。這裏是經過使用Context.getApplicationContext()
方法來實現。
在Android中咱們會使用不少listener,observer。這些都是做爲觀察者模式的實現。當咱們註冊一個listener時,這個listener的實例會被Activity所引用。若是listener的生命週期要明顯大於Activity,那麼就有可能發生內存泄漏。
public class MainActivity extends AppCompatActivity implements OnNetworkChangedListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NetworkManager.getInstance().registerListener(this);
}
@Override
public void onNetworkUp() {
}
@Override
public void onNetworkDown() {
}
}
複製代碼
解決辦法: 在Activity onDestroy()時調用unregisterListener方法,解綁。
見下面的代碼:
public class MainActivity extends AppCompatActivity
{
private static Leak leak;
@Override
protected void onCreate( @Nullable Bundle savedInstanceState )
{
super.onCreate( savedInstanceState );
setContentView( R.layout.activity_main );
leak = new Leak();
}
private class Leak{
}
}
複製代碼
非靜態內部類會默認持有外部類的引用,當MainActivity銷燬重建後因爲其內部類Leak持有了它的引用,而且Leak是靜態的,生命週期和應用同樣長,所以致使LeakActivity沒法被銷燬,所以一直存在於內存中。 要銷燬MainActivity,必須先銷燬leak,可是要銷燬mLeak,必須先銷燬LeakActivity,因此一個也不能銷燬。就形成了內存泄漏。
經過反編譯後就能清楚的看出來了
解決辦法: 1.及時銷燬 2.放到Application中
下面主要分析下內存泄漏檢測工具leakcanary
引入方法,只需下面兩步:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
public class StudyApplication extends Application
{
@Override
public void onCreate()
{
super.onCreate();
if( LeakCanary.isInAnalyzerProcess( this ) )
{
return;
}
LeakCanary.install( this );
}
}
複製代碼
LeakCanary.install( this );
開始/**
* Creates a {@link RefWatcher} that works out of the box, and starts watching activity
* references (on ICS+).
*/
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
複製代碼
refWatcher(application)
建立AndroidRefWatcherBuilder對象listenerServiceClass(DisplayLeakService.class)
建立解析內存泄漏信息的服務,這裏也能夠傳遞繼承AbstractAnalysisResultService的自定義對象,用於上傳內存泄漏信息excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
設置過濾,過濾掉安卓系統自己出現的內存泄漏現象,只保留用戶app出現的內存泄漏buildAndInstall()
構造RefWatcher對象,並返回public @NonNull RefWatcher buildAndInstall() {
...
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
...
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
...
return refWatcher;
}
複製代碼
這裏有2個重要的操做build();
,ActivityRefWatcher.install(context, refWatcher);
第一個方法用於生成RefWatcher
它是用來監控引用的工具,第二個方法,建立了ActivityRefWatcher,這個就是用來監控Activity泄漏情況的其中:
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
複製代碼
監控的遠離就是在application中註冊了一個ActivitylifecycleCallbacks的回調函數,用來監聽Application整個生命週期中全部Activity的lifecycle事件。而這個lifecycleCallbacks,就是
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
public abstract class ActivityLifecycleCallbacksAdapter
implements Application.ActivityLifecycleCallbacks {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override public void onActivityStarted(Activity activity) {
}
@Override public void onActivityResumed(Activity activity) {
}
@Override public void onActivityPaused(Activity activity) {
}
@Override public void onActivityStopped(Activity activity) {
}
@Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override public void onActivityDestroyed(Activity activity) {
}
}
複製代碼
它只監聽了全部Activity的onActivityDestroyed事件,當Activity被destroy時,調用refWatcher.watch(activity);
函數,將目標activity對象傳遞到RefWatcher,讓它去監控這個activity是否被回收了,若是沒有被回收,則發生了內存泄漏事件。
refWatcher.watch(activity)
方法public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
複製代碼
checkNotNull(watchedReference, "watchedReference")
判斷watchedReference是否爲空,若是爲空,就不須要繼續進行下去了,接下來是構造了一個惟一key,並傳入retainedKeys中,咱們想要觀測的activity對應的惟一key都會存放到集合裏面,以後把咱們傳入的activity包裝成一個KeyedWeakReference(能夠看成WeakReference),而後執行ensureGoneAsync
這個方法最後會執行一個Runnable,調用ensureGone(reference, watchStartNanoTime);
咱們知道watch
函數自己就是用來監聽activity是否被回收掉了,這就涉及到兩個問題:
對於這個ensureGone(reference, watchStartNanoTime);
函數它作的事情就是確保reference被回收掉了,不然就意味着內存泄漏
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
複製代碼
這個watchExecutor在LeakCanary中是AndroidWatchExecutor的實例,調用它的execute方法實際上就是向主線程的消息隊列中插入了一個IdleHandler消息,這個消息只有在對應的消息隊列爲空的時候纔會去執行,所以RefWatcher的watch方法就保證了在主線程空閒的時候纔會去執行ensureGone方法,防止由於內存泄漏檢查任務而嚴重影響應用的正常執行.
在說下面以前,先解釋下WeakReference
和ReferenceQueue
的工做原理
弱引用WeakReference 被強引用的對象就算髮生 OOM 也永遠不會被垃圾回收機回收;被弱引用的對象,只要被垃圾回收器發現就會當即被回收;被軟引用的對象,具有內存敏感性,只有內存不足時纔會被回收,經常使用來作內存敏感緩存器;虛引用則任意時刻均可能被回收,使用較少。
引用隊列 ReferenceQueue 咱們經常使用一個 WeakReference reference = new WeakReference(activity);,這裏咱們建立了一個 reference 來弱引用到某個 activity,當這個 activity 被垃圾回收器回收後,這個 reference 會被放入內部的 ReferenceQueue 中。也就是說,從隊列 ReferenceQueue 取出來的全部 reference,它們指向的真實對象都已經成功被回收了。
ensureGone(reference, watchStartNanoTime)
檢測回收方法先看實現的代碼:
@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences();
// 若是正在debug斷點調試,則延遲執行檢查(由於斷點會影響準確性)
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {
return DONE;
}
// 若是沒有被回收,則觸發一次GC
gcTrigger.runGc();
// 再次將已回收的對象對應的key從retainedKeys中移除
removeWeaklyReachableReferences();
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
// 獲取堆內存鏡像文件
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
heapdumpListener.analyze(heapDump);
}
return DONE;
}
複製代碼
基於咱們對ReferenceQueue的瞭解,只要把隊列中全部的reference取出來,並把對應的retainedKeys 裏的 key 移除,剩下的 key 對應的對象都沒有被回收。
具體步驟就是:
removeWeaklyReachableReferences();
把已被回收的對象key從retainedKeys中移除,剩下的key都是未被回收的對象if (gone(reference))
來判斷某個reference的key是否還在retainedKey集合中,若不在,表示以及被回收了,不然繼續gcTrigger.runGc();
手動觸發gc,當即把全部WeakReference 引用的對象回收removeWeaklyReachableReferences();
再次清理retainedKeys,若是該 reference 還在 retainedKeys 裏(if (!gone(reference)))
,表示泄漏heapDumper.dumpHeap()
將內存狀況dump成文件,併發送Notification,以及ToastheapdumpListener.analyze(heapDump);
進行分析至此Leakcanary檢測到內存泄漏的流程就看完了。
如今來主要看下調用heapdumpListener.analyze(heapDump);
後進行的分析
@Override public void analyze(@NonNull HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
複製代碼
這個方法最後調用HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
進行分析,傳遞的參數context,heapDump,以及分析完畢後的回調類。HeapAnalyzerService
繼承自ForegroundService
用於分析,並運行在另外一個獨立進程中,而runAnalysis
方法最後調用ContextCompat.startForegroundService(context, intent);
來啓動這個service
在service啓動後調用onHandleIntentInForeground
方法
@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
HeapAnalyzer heapAnalyzer =
new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
複製代碼
這裏分析內存的主要操做在HeapAnalyzer當中,分析完成後獲取內存泄漏點以及引用鏈 AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
獲取結果後,進行回調,回調給以前設置的listenerClassName的那個類,也能夠是開發者本身繼承的自定義類。
heapAnalyzer.checkForLeak()
方法進行內存分析public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,
@NonNull String referenceKey,
boolean computeRetainedSize) {
...
try {
listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
listener.onProgressUpdate(PARSING_HEAP_DUMP);
Snapshot snapshot = parser.parse();
listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
// 從內存鏡像中獲取全部的GC Roots,並將它們添加到一個集合中
deduplicateGcRoots(snapshot);
listener.onProgressUpdate(FINDING_LEAKING_REF);
// 使用反射,經過key找到泄露的對象實例
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
// False alarm, weak reference was cleared in between key check and heap dump.
if (leakingRef == null) {
String className = leakingRef.getClassObj().getClassName();
return noLeak(className, since(analysisStartNanoTime));
}
//查找泄露路徑
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
複製代碼
這個方法第一步就是利用Haha庫將以前dump出來的內存文件解析成Snapshot對象,其中調用到的方法包括SnapshotFactory的parse和HprofIndexBuilder的fill方法。。解析獲得的Snapshot對象直觀上和咱們使用MAT進行內存分析時候羅列出內存中各個對象的結構很類似,它經過對象之間的引用鏈關係構成了一棵樹,咱們能夠在這個樹種查詢到各個對象的信息,包括它的Class對象信息、內存地址、持有的引用及被持有的引用關係等。
以後LeakCanary就須要在Snapshot中找到一條有效的到被泄漏對象之間的引用路徑。首先它調用findLeakingReference方法來從Snapshot中找到被泄漏對象 重要的方法有2個
findLeakingReference(referenceKey, snapshot);
獲取內存泄漏實例findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
分析對應的引用鏈先來看第一個findLeakingReference
private Instance findLeakingReference(String key, Snapshot snapshot) {
ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
if (refClass == null) {
throw new IllegalStateException(
"Could not find the " + KeyedWeakReference.class.getName() + " class in the heap dump.");
}
List<String> keysFound = new ArrayList<>();
for (Instance instance : refClass.getInstancesList()) {
List<ClassInstance.FieldValue> values = classInstanceValues(instance);
Object keyFieldValue = fieldValue(values, "key");
if (keyFieldValue == null) {
keysFound.add(null);
continue;
}
String keyCandidate = asString(keyFieldValue);
if (keyCandidate.equals(key)) {//匹配key
return fieldValue(values, "referent");//定位泄漏對象
}
keysFound.add(keyCandidate);
}
throw new IllegalStateException(
"Could not find weak reference with key " + key + " in " + keysFound);
}
複製代碼
爲了可以準確找到被泄漏對象,LeakCanary經過被泄漏對象的弱引用來在Snapshot中定位它。由於,若是一個對象被泄漏,必定也能夠在內存中找到這個對象的弱引用,再經過弱引用對象的referent就能夠直接定位被泄漏對象。
上面的方法實現了內存泄漏的實例查找。下一步的工做就是找到一條有效的到被泄漏對象的最短的引用,這經過findLeakTrace來實現,其代碼以下
private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
Instance leakingRef, boolean computeRetainedSize) {
listener.onProgressUpdate(FINDING_SHORTEST_PATH);
ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
String className = leakingRef.getClassObj().getClassName();
// False alarm, no strong reference path to GC Roots.
if (result.leakingNode == null) {
return noLeak(className, since(analysisStartNanoTime));
}
listener.onProgressUpdate(BUILDING_LEAK_TRACE);
// 根據查找的結果,創建泄露路徑
LeakTrace leakTrace = buildLeakTrace(result.leakingNode);
long retainedSize;
if (computeRetainedSize) {
listener.onProgressUpdate(COMPUTING_DOMINATORS);
// Side effect: computes retained size.
snapshot.computeDominators();
Instance leakingInstance = result.leakingNode.instance;
retainedSize = leakingInstance.getTotalRetainedSize();
// TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
if (SDK_INT <= N_MR1) {
listener.onProgressUpdate(COMPUTING_BITMAP_SIZE);
retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
}
} else {
retainedSize = AnalysisResult.RETAINED_HEAP_SKIPPED;
}
return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
since(analysisStartNanoTime));
}
複製代碼
此方法中pathFinder.findPath(snapshot, leakingRef);
用於獲取最短GC ROOT路徑,第一個參數是帶有全部信息的snapshot對象,第二個參數是內存泄漏的那個類的封裝對象,使用算法廣度優先搜索法
findLeakTrace 方法整體的邏輯就是
Result findPath(Snapshot snapshot, Instance leakingRef) {
clearState();
canIgnoreStrings = !isString(leakingRef);
// 將上面找到的全部GC Roots添加到隊列中
enqueueGcRoots(snapshot);
boolean excludingKnownLeaks = false;
LeakNode leakingNode = null;
// 若是將從GC Root開始的全部引用看作樹,則這裏就能夠理解成使用廣度優先搜索遍歷引用「森林」
// 若是將全部的引用都看作是長度爲1的Edge,那麼這些引用就組成了一幅有向圖,
// 這裏就是使用相似Dijkstra算法的方法來尋找最短路徑,越在隊列後面的,距離GC Roots越遠
while (!toVisitQueue.isEmpty() || !toVisitIfNoPathQueue.isEmpty()) {
LeakNode node;
// 若是toVisitQueue中沒有元素,則取toVisitIfNoPathQueue中的元素
// 意思就是,若是遍歷完了toVisitQueue尚未找到泄露的路徑,那麼就繼續遍歷設置了「例外」的那些對象
// 「例外」是什麼狀況?在後續兩個方法會講到。
if (!toVisitQueue.isEmpty()) {
node = toVisitQueue.poll();
} else {
node = toVisitIfNoPathQueue.poll();
if (node.exclusion == null) {
throw new IllegalStateException("Expected node to have an exclusion " + node);
}
excludingKnownLeaks = true;
}
// Termination
if (node.instance == leakingRef) {
leakingNode = node;
break;
}
// 由於一個對象能夠被多個對象引用,以GC Root爲根的引用樹
// 並非嚴格意義上的樹,因此若是已經遍歷過當前對象,就跳過
if (checkSeen(node)) {
continue;
}
// 下面是根據當前引用節點的類型,分別找到它們所引用的對象
if (node.instance instanceof RootObj) {
visitRootObj(node);
} else if (node.instance instanceof ClassObj) {
visitClassObj(node);
} else if (node.instance instanceof ClassInstance) {
visitClassInstance(node);
} else if (node.instance instanceof ArrayInstance) {
visitArrayInstance(node);
} else {
throw new IllegalStateException("Unexpected type for " + node.instance);
}
}
// 返回查找結果
return new Result(leakingNode, excludingKnownLeaks);
}
複製代碼
其中enqueueGcRoots(snapshot);
會遍歷獲取全部的GCROOT並放入搜索隊列中 while (!toVisitQueue.isEmpty() || !toVisitIfNoPathQueue.isEmpty())
循環條件優先找toVisitQueue隊列,找完在找toVisitIfNoPathQueue隊列,而路徑中包含toVisitIfNoPathQueue裏的元素則標示excludingKnownLeaks爲true
爲了說明excludingKnownLeaks,以visitClassInstance(node);
代碼爲例
private void visitClassInstance(LeakNode node) {
ClassInstance classInstance = (ClassInstance) node.instance;
Map<String, Exclusion> ignoredFields = new LinkedHashMap<>();
ClassObj superClassObj = classInstance.getClassObj();
Exclusion classExclusion = null;
// 將設置了「例外」的對象記錄下來
// 這裏的「例外」就是上一個方法中提到的「例外」。是指那些低優先級的,或者說幾乎不可能引起內存泄露的對象
// 例如SDK中的一些對象,諸如Message, InputMethodManager等,通常狀況下,這些對象都不會致使內存泄露。
// 所以只有在遍歷了其餘對象以後,找不到泄露路徑的狀況下,才遍歷這些對象。
while (superClassObj != null) {
Exclusion params = excludedRefs.classNames.get(superClassObj.getClassName());
if (params != null) {
// true overrides null or false.
// 若是當前類或者其父類被設置了「例外」,則將其賦值給classExclusion
if (classExclusion == null || !classExclusion.alwaysExclude) {
classExclusion = params;
}
}
// 若是當前類及其父類包含例外的成員,將這些成員添加到ignoredFields中
Map<String, Exclusion> classIgnoredFields =
excludedRefs.fieldNameByClassName.get(superClassObj.getClassName());
if (classIgnoredFields != null) {
ignoredFields.putAll(classIgnoredFields);
}
superClassObj = superClassObj.getSuperClassObj();
}
if (classExclusion != null && classExclusion.alwaysExclude) {
return;
}
// 遍歷每個成員
for (ClassInstance.FieldValue fieldValue : classInstance.getValues()) {
Exclusion fieldExclusion = classExclusion;
Field field = fieldValue.getField();
// 若是成員不是對象,則忽略
if (field.getType() != Type.OBJECT) {
continue;
}
// 獲取成員實例
Instance child = (Instance) fieldValue.getValue();
String fieldName = field.getName();
Exclusion params = ignoredFields.get(fieldName);
// 若是當前成員對象是例外的,而且當前類和全部父類都不是例外的(classExclusion = null),
// 或,若是當前成員對象時例外的,並且是alwaysExclude,並且當前類和父類都不是alwaysExclude
// 則認爲當前成員是須要例外處理的。
// 這個邏輯很繞,實際上「||」後面的判斷是不須要的,具體在enqueue方法中講。
if (params != null && (fieldExclusion == null || (params.alwaysExclude
&& !fieldExclusion.alwaysExclude))) {
fieldExclusion = params;
}
// 入隊列
enqueue(fieldExclusion, node, child, fieldName, INSTANCE_FIELD);
}
}
複製代碼
再看下enqueue()
方法
private void enqueue(Exclusion exclusion, LeakNode parent, Instance child, String referenceName,
LeakTraceElement.Type referenceType) {
if (child == null) {
return;
}
if (isPrimitiveOrWrapperArray(child) || isPrimitiveWrapper(child)) {
return;
}
// Whether we want to visit now or later, we should skip if this is already to visit.
if (toVisitSet.contains(child)) {
return;
}
// 這個exclusion就是上一個方法經過「很繞的」邏輯判斷的出來的
// 這裏的做用就是若是爲null則visitNow,這個boolean值在下面會用到。
// 能夠看到這裏只是判斷exclusion是否爲null,並無使用到alwaysExclude參數,
// 因此說上一個方法中,「||」以後的判斷是沒有必要的。
boolean visitNow = exclusion == null;
if (!visitNow && toVisitIfNoPathSet.contains(child)) {
return;
}
if (canIgnoreStrings && isString(child)) {
return;
}
if (visitedSet.contains(child)) {
return;
}
LeakNode childNode = new LeakNode(exclusion, child, parent, referenceName, referenceType);
// 這裏用到了boolean值visitNow,就是說若是exclusion對象爲null,則表示這不是一個例外的對象(暫且稱之爲常規對象);
// 若是exclusion對象不爲null,則表示這個對象是例外對象,只有在遍歷全部常規對象以後,仍是找不到路徑的狀況下才會被遍歷。
if (visitNow) {
toVisitSet.add(child);
toVisitQueue.add(childNode);
} else {
toVisitIfNoPathSet.add(child);
toVisitIfNoPathQueue.add(childNode);
}
}
複製代碼
查找最短強引用路徑的流程以下圖:
下面附一張leakcanary的流程圖
Shark是Leakcanary 2.0.0時推出的Heap分析工具,替代了以前使用的HAHA庫,其做者稱它比haha使用的perflib快6倍,使用的內存倒是以前的10分之一 Shark文件架構以下:
dependencies {
implementation 'com.squareup.leakcanary:shark-android:$sharkVersion'
}
複製代碼