參考文檔
http://blog.csdn.net/wyfei021/article/details/46506521
http://vjson.com/wordpress/leakcanary%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90%e7%ac%ac%e4%b8%89%e8%ae%b2%ef%bc%8dheapanalyzerservice%e8%af%a6%e8%a7%a3.html
http://blog.csdn.net/u011291205/article/details/52497078
http://www.jianshu.com/p/481775d198f0
http://www.jianshu.com/p/5ee6b471970e
http://blog.csdn.net/cloud_huan/article/details/53081120
http://vjson.com/wordpress/leakcanary%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90%e7%ac%ac%e4%b8%80%e8%ae%b2.html
http://www.cnblogs.com/xgjblog/p/6084388.html
leakcanary 內存泄露監測工具
https://github.com/square/leakcanary
源碼流程分析
官方demo
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); View button = findViewById(R.id.async_task); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startAsyncTask(); } }); } void startAsyncTask() { //內存泄漏原因分析 匿名類隱式持有外部類MainActivity的引用 所以假如任務完成前Activity //銷毀了(例如轉屏)則activity對象會發生內存泄漏 new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { //后天做一些耗時任務 SystemClock.sleep(20000); return null; } }.execute(); } }
public class ExampleApplication extends Application { @Override public void onCreate() { super.onCreate(); if (LeakCanary.isInAnalyzerProcess(this)) { // This process is dedicated to LeakCanary for heap analysis. // You should not init your app in this process. return; } enabledStrictMode(); LeakCanary.install(this); } private static void enabledStrictMode() { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() // .detectAll() // .penaltyLog() // .penaltyDeath() // .build()); } }
開始ExampleApplication
LeakCanary.install(this);
LeakCanary
install靜態方法里面包含了3個方法,這3個方法都是通過AndroidRefWatcherBuilder這個輔助類來配置相關信息的
listenerServiceClass方法是和結果分析相關的服務綁定,綁定到DisplayLeakService.class這個類上面,這個類負責通知泄漏消息給你
excludedRefs方法是排除一些開發可以忽略的泄漏路徑(一般是系統級別BUG),這些枚舉在AndroidExcludedRefs這個類當中定義
buildAndInstall這才是重點方法
public static RefWatcher install(Application application) { return refWatcher(application).listenerServiceClass(DisplayLeakService.class) .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) .buildAndInstall(); }
AndroidRefWatcherBuilder
實例化RefWatcher對象,這個對象是用來判斷泄漏對象的
AndroidRefWatcherBuilder public RefWatcher buildAndInstall() { RefWatcher refWatcher = build(); if (refWatcher != DISABLED) { LeakCanary.enableDisplayLeakActivity(context); ActivityRefWatcher.install((Application) context, refWatcher); } return refWatcher; }
ActivityRefWatcher 核心方法找到了,就是registerActivityLifecycleCallbacks這個方法,這是application提供的一個方法,用來統一管理所有activity的生命周期,LeakCanay是在onDestory()方法實現監控的
public static void install(Application application, RefWatcher refWatcher) { new ActivityRefWatcher(application, refWatcher).watchActivities(); } public void watchActivities() { // Make sure you don't get installed twice. stopWatchingActivities(); application.registerActivityLifecycleCallbacks(lifecycleCallbacks); }
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks = new 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) { ActivityRefWatcher.this.onActivityDestroyed(activity); } }; void onActivityDestroyed(Activity activity) { refWatcher.watch(activity); }
之后把activity直接扔給RefWatcher類處理,所以第一部分完成
第二部分 RefWatcher
上面做的事情就是把activity對象封裝成帶key值和帶引用隊列(ReferenceQueue)的KeyedWeakReference對象,key值是用來最終定位泄漏對象用的,
第三部分會用到,引用隊列是用來監控弱引用回收的,這個類是繼承WeakReference類,所以封裝完你會看到這個類的一些特點:
1.帶key,通過UUID.randomUUID().toString(),是唯一key序列
2.包裝成WeakReference並添加到ReferenceQueue
public void watch(Object watchedReference) { watch(watchedReference, ""); } 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); }
觸發watch動作過程分析:當Activity的onDestroy調用的時候,Application會收到通知,然后調用
lifecycleCallback.onActivityDestroyed()方法,最終RefWatcher的watch方法被觸發,也就實現
了Activity內存泄漏分析自動分析。
繼續走 進入核心代碼
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
//在異步線程上開始分析這個弱引用 watchExecutor.execute(new Retryable() { @Override public Retryable.Result run() { return ensureGone(reference, watchStartNanoTime); } }); } Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) { long gcStartNanoTime = System.nanoTime(); long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); //移除弱引用 removeWeaklyReachableReferences(); if (debuggerControl.isDebuggerAttached()) { //如果VM正連接到Debuger,忽略這次檢測,因為Debugger可能會持有一些在當前上下文中不可見的對象,導致誤判 return RETRY; } if (gone(reference)) {//如果引用已經不存在了則返回 return DONE; } gcTrigger.runGc();//觸發GC removeWeaklyReachableReferences();//再次移除弱引用,二次確認 if (!gone(reference)) {//如果GC之后引用還是存在,那么就進行深入分析 long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); File heapDumpFile = heapDumper.dumpHeap();//dump內存 if (heapDumpFile == RETRY_LATER) { // Could not dump the heap. return RETRY; } long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); heapdumpListener.analyze(//分析Hprof文件 new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs)); } return DONE; }
如果這個對象作為弱引用,被回收了,那么添加到引用隊列(ReferenceQueue)當中去,所以這個函數.poll是出棧的意思,
如果成功出棧了,那么說明你加入了引用隊列,然后可以認為是已經被回收了的,然后retainedKeys這個是一個Set容器,
在之前會加入生成的唯一key作為標識,這里如果這個對象回收了,那么就移除這個key值。
然后是gone函數,就是看retainedKeys容器有沒有key,如果回收了,就不存在key了,那么就沒有泄漏,否則就懷疑有泄漏。
然后后面的手動GC和檢查都是一個類似二次確認的道理,還是沒有回收,那么才會進入精確階段,.hropf分析大法,這是第三部分內容。
private boolean gone(KeyedWeakReference reference) { return !retainedKeys.contains(reference.key); } private void removeWeaklyReachableReferences() { // WeakReferences are enqueued as soon as the object to which they point to becomes weakly // reachable. This is before finalization or garbage collection has actually happened. KeyedWeakReference ref; while ((ref = (KeyedWeakReference) queue.poll()) != null) { retainedKeys.remove(ref.key); } }
內存快照分析
那么在第二步根據弱引用有沒有回收這個上已經是基本確定了這個對象有沒有泄漏,
那么下一部就是獲取dumpheap以及對這個dumpheap進行analyze。
調用就是下面這幾行,然而這幾行的背后其實引用的又是另外一個項目haha,具體的開源地址為:https://github.com/square/haha
這邊只是接口
public interface Listener { Listener NONE = new Listener() { @Override public void analyze(HeapDump heapDump) { } }; void analyze(HeapDump heapDump); }
具體實現是在 ServiceHeapDumpListener
@Override public void analyze(HeapDump heapDump) { checkNotNull(heapDump, "heapDump"); HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass); }
繼續封裝 HeapAnalyzerService
public static void runAnalysis(Context context, HeapDump heapDump, Class<? extends AbstractAnalysisResultService> listenerServiceClass) { Intent intent = new Intent(context, HeapAnalyzerService.class); intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName()); intent.putExtra(HEAPDUMP_EXTRA, heapDump); context.startService(intent); } @Override protected void onHandleIntent(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); AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey); AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result); }
checkForLeak就是最為關鍵的方法 進入HeapAnalyzer
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) { long analysisStartNanoTime = System.nanoTime(); if (!heapDumpFile.exists()) { Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile); return failure(exception, since(analysisStartNanoTime)); } try { HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile); HprofParser parser = new HprofParser(buffer);//解析器解析文件 Snapshot snapshot = parser.parse();//解析過程,是基於google的perflib庫,根據hprof的格式進行解析 deduplicateGcRoots(snapshot);//分析結果進行去重
//此方法就是根據我們需要檢測的類的key,查詢解析結果中是否有我們的對象,獲取解析結果中我們檢測的對象 Instance leakingRef = findLeakingReference(referenceKey, snapshot); //此對象不存在表示已經被gc清除了,不存在泄露因此返回無泄漏 if (leakingRef == null) { return noLeak(since(analysisStartNanoTime)); }
//此對象存在也不能也不能確認它內存泄漏了,要檢測此對象的gc root return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef); } catch (Throwable e) { return failure(e, since(analysisStartNanoTime)); } }
上面的checkForLeak方法就是輸入.hprof,輸出分析結果,主要有以下幾個步驟:
1.把.hprof轉為Snapshot,這個Snapshot對象就包含了對象引用的所有路徑
2.精簡gcroots,把重復的路徑刪除,重新封裝成不重復的路徑的容器
3.找出泄漏的對象
4.找出泄漏對象的最短路徑
private Instance findLeakingReference(String key, Snapshot snapshot) {
//因為需要檢測的類都構造了一個KeyedWeakReference,因此先找到KeyedWeakReference,就可以找到我們的對象 ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName()); List<String> keysFound = new ArrayList<>();
//循環所有KeyedWeakReference實例 for (Instance instance : refClass.getInstancesList()) { List<ClassInstance.FieldValue> values = classInstanceValues(instance);
//找到KeyedWeakReference里面的key值,此值在我們前面傳入的對象唯一標示 String keyCandidate = asString(fieldValue(values, "key")); 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); }
那么上面這個方法是在snapshot快照中找到第一個弱引用(因為就是這個對象沒有回收,泄漏了嘛),然后根據遍歷這個對象的所有實例,
如果key值和最開始定義封裝的key值相同,那么返回這個泄漏對象,就是已近在快照中定位到了泄漏對象了。
private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot, Instance leakingRef) { //這兩行代碼是判斷內存泄露的關鍵,我們在上篇中分析hprof文件,判斷內存泄漏
//判斷的依據是展開調用到gc root,所謂gc root,就是不能被gc回收的對象,
//gc root有很多類型,我們只要關注兩種類型1.此對象是靜態 2.此對象被其他線程使用,並且其他線程正在運行,沒有結束
//pathFinder.findPath方法中也就是判斷這兩種情況 ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs); ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef); // 找不到引起內存泄漏的gc root,就表示此對象未泄漏 if (result.leakingNode == null) { return noLeak(since(analysisStartNanoTime)); } //生成泄漏的調用棧,為了在通知欄中顯示 LeakTrace leakTrace = buildLeakTrace(result.leakingNode); String className = leakingRef.getClassObj().getClassName(); // Side effect: computes retained size. snapshot.computeDominators(); Instance leakingInstance = result.leakingNode.instance; //計算泄漏的空間大小 long retainedSize = leakingInstance.getTotalRetainedSize(); // TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer if (SDK_INT <= N_MR1) { retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance); } return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize, since(analysisStartNanoTime)); }
最后一個步驟是根據上一部得到的泄漏對象找到最短路徑,封裝在ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);這句中、
總結:在第三個分析步驟,解析hprof文件中,是先把這個文件封裝成snapshot,然后根據弱引用和前面定義的key值,確定泄漏的對象,最后找到最短泄漏路徑,作為結果反饋出來,
那么如果在快照中找不到這個懷疑泄漏的對象,那么就認為這個對象其實並沒有泄漏,因為已經回收了
總結
LeakCanay的入口是在application的onCreate()方法中聲明的,其實用的就是Application的ActivityLifecycleCallbacks回調接口監聽所有activity的onDestory()的,
在這個方法進行RefWatcher.watch對這個對象進行監控。
具體是這樣做的,封裝成帶key的弱引用對象,然后GC看弱引用對象有沒有回收,沒有回收的話就懷疑是泄漏了,需要二次確認。然后生成HPROF文件
源碼API分析
首先分析系統API
Application ActivityLifecycleCallbacks
作用 Application通過此接口提供了一套回調方法,用於讓開發者對Activity的生命周期事件進行集中處理。僅限4.0以后的版本使用
public class BaseApplication extends Application { @Override public void onCreate() { super.onCreate(); //Application下的每個Activity聲明周期改變時,都會觸發以下的函數 registerActivityLifecycleCallbacks(new 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) { Toast.makeText(getBaseContext(), "onActivityDestroyed", Toast.LENGTH_SHORT).show(); } }); } }
System.nanoTime()
作用 提供相對精確的計時 給一些性能測試提供了更准確的參考
與System.currentTime()區別 System.currentTime()的精度是毫秒 返回值是從1970.1.1的零點開始到當前時間的毫秒數,理論上這個可以用來算當前的時間
System.nanoTime() 輸出的精度是納秒級 它的返回值是一個從確定的值算起的,但是這個值是任意的,可能是一個未來的時間,所以返回值有可能是負數
long watchStartNanoTime = System.nanoTime(); Toast.makeText(getBaseContext(), "watchStartNanoTime" + watchStartNanoTime, Toast.LENGTH_SHORT).show(); long gcStartNanoTime = System.nanoTime(); long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); //NANOSECONDS 納秒 時間單位 一秒的10億分之一 Toast.makeText(getBaseContext(), "watchDurationMs" + watchDurationMs, Toast.LENGTH_SHORT).show();
UUID.randomUUID()
作用 提供的一個自動生成主鍵的方法 在一台機器上生成的數字保證對在同一時空中的所有機器都是唯一的
String key = UUID.randomUUID().toString();
Toast.makeText(getBaseContext(), "key" + key, Toast.LENGTH_SHORT).show();
CopyOnWriteArraySet
作用 一個線程安全的容器 底層實現是CopyOnWriteArrayList 適用大小通常保持很小 只讀操作遠多於可變操作的場景
ReferenceQueue
作用 在適當的時候檢測到對象的可達性發生改變后,垃圾回收器就將已注冊的引用對象添加到此隊列中
后面代碼里使用一個弱引用連接到你需要檢測的對象,然后使用ReferenceQueue來監測這個弱引用可達性的改變
四大引用
StrongReference
SoftReference
WeakReference
GC線程掃描它所管轄的內存區域時,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。
由於垃圾回收器是一個優先級很低的線程,因此不一定會很快發現那些只具有弱引用的對象。
代碼示例
A a = new A(); ReferenceQueue queue = new ReferenceQueue(); WeakReference aa = new WeakReference(a, queue); a = null; Runtime.getRuntime().gc(); System.runFinalization(); try { //TOOD線程睡覺
Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Reference poll = null; while ((poll = queue.poll()) != null) { System.out.println(poll.toString()); }
WeakReference和ReferenceQueue聯合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就
會把這個弱引用加入到與之關聯的引用隊列中。上圖中的實線a表示強引用,虛線aa表示弱引用。
如果切斷a,那么Object對象將會被回收。
當垃圾回收器回收對象的時候,aa這個弱引用將會入隊進入ReferenceQueue,所以queue.poll()將
不會為空,除非這個對象沒有被垃圾回收器清理。
PhantomReference
代碼執行GC操作
// Code taken from AOSP FinalizationTest:
// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
// java/lang/ref/FinalizationTester.java
// System.gc() does not garbage collect every time. Runtime.gc() is
// more likely to perfom a gc.
Runtime.getRuntime().gc();
enqueueReferences();
System.runFinalization();
AOSP Android 開放源代碼項目
Java里面的gc擁有各種各樣的垃圾收集算法,gc的執行具有很大的不確定性。不管是用System.gc()還是Runtime.getRuntime().gc(),都是對jvm的一個建議,
引發jvm的內部垃圾算法的加權,無法保證gc一定馬上執行。所以即使sleep了一定的時間等待gc,仍有極大的可能未執行GC
弱引用算法總結
Object object = new Object(); //強引用 ReferenceQueue queue = new ReferenceQueue(); WeakReference aa = new WeakReference(object, queue); //弱引用aa與強引用object和引用隊列queue關聯 object = null; //強引用置空 //手動GC算法 // Code taken from AOSP FinalizationTest: // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/ // java/lang/ref/FinalizationTester.java // System.gc() does not garbage collect every time. Runtime.gc() is // more likely to perfom a gc. Runtime.getRuntime().gc(); try { Thread.sleep(100); } catch (InterruptedException e) { throw new AssertionError(); } System.runFinalization(); //如果強引用object被回收 則弱引用aa會放入引用隊列中 Reference poll = null; while ((poll = queue.poll()) != null) { Toast.makeText(getBaseContext(), "poll.toString()" + poll.toString(), Toast.LENGTH_SHORT).show(); }
將弱引用與要回收的強引用和引用隊列關聯
如果強引用回收了 則弱引用會放入引用隊列中
如果所有強引用都回收了 則無內存泄露 相關聯的弱引用則都在引用隊列中
問題是手動GC具有很大的不確定性 無法保證GC一定執行
IntentService
作用 一個異步的會自動停止的服務 很好解決了傳統Service中處理耗時操作忘記停止銷毀Service的問題
底層實現是HandlerThread和Handler的封裝
項目API
ActivityRefWatcher
用於監控Activity,但只能用於Android 4.0及其之上 它通過watchActivities方法將全局的Activity生命周期回調接口
Application.ActivityLifecycleCallbacks注冊到application
RefWatcher
作用 LeakCanary核心中的核心。RefWatcher的工作就是觸發GC,如果對象被回收,那么WeakReference將被放入
ReferenceQueue中,否則就懷疑有泄漏(僅僅是懷疑),然后將內存dump出來,為接下來的深入分析做准備。
watchExecutor: 執行內存泄露檢測的executor
debuggerControl :用於查詢是否正在調試中,調試中不會執行內存泄露檢測
queue : 用於判斷弱引用所持有的對象是否已被GC。
gcTrigger: 用於在判斷內存泄露之前,再給一次GC的機會
headDumper: 用於在產生內存泄露室執行dump 內存heap
heapdumpListener: 用於分析前面產生的dump文件,找到內存泄露的原因
excludedRefs: 用於排除某些系統bug導致的內存泄露
retainedKeys: 持有那些待檢測以及產生內存泄露的引用的key。
工作流程
RefWatcher.watch() 創建一個 KeyedWeakReference 到要被監控的對象。
然后在后台線程檢查引用是否被清除,如果沒有,調用GC。
如果引用還是未被清除,把 heap 內存 dump 到 文件系統中的一個 .hprof 文件中。
在另外一個進程中,HeapAnalyzerService 通過 HeapAnalyzer 使用HAHA 解析這個文件。
得益於唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位內存泄漏。
HeapAnalyzer 計算 到 GC roots 的最短強引用路徑,並確定是否泄漏。如果是,建立導致泄漏的引用鏈。
引用鏈傳遞到 APP 進程中的 DisplayLeakService, 並以通知的形式展示出來。
ExcludedRef
系統的bug,以及某些廠商rom的bug
AndroidExcludedRefs
枚舉了很多特定版本系統issue引起的內存泄漏,因為這種問題不是開發者導致的,分析內存泄露時會排除掉
HeapDump.Listener與ServiceHeapDumpListener
ServiceHeapDumpListener實現了HeapDump.Listener接口。當RefWatcher發現可疑引用的之后,它將dump出來的Hprof文件通過
listener傳遞到HeapAnalyzerService。
HeapAnalyzerService
主要是通過HeapAnalyzer.checkForLeak分析對象的引用,計算出到GC
root的最短強引用路徑。然后將分析結果傳遞給DisplayLeakService。
AbstractAnalysisResultService與DisplayLeakService
DisplayLeakService繼承了AbstractAnalysisResultService。它主要是用來處理分析結果,將結果寫入文件,然后在通知欄報警。
開頭的兩個問題
如何判斷一個對象屬於內存泄漏
如何在hprof文件里面定位到問題並抓取出來