[摘要:应用 参考我以前写的《Android 内存泄露对象应用》 监控 Activity 泄漏 咱们经常把 Activity 当作为 Context 工具应用,正在分歧场所由种种工具援用 Activity。以是,] java
http://www.jianshu.com/p/0049e9b344b0node
参考我以前写的《Android 内存泄漏工具使用》android
咱们常常把 Activity 看成为 Context 对象使用,在不一样场合由各类对象引用 Activity。因此,Activity 泄漏是一个重要的须要检查的内存泄漏之一。算法
public class ExampleApplication extends Application { public static RefWatcher getRefWatcher(Context context) { ExampleApplication application = (ExampleApplication) context.getApplicationContext(); return application.refWatcher; } private RefWatcher refWatcher; @Override public void onCreate() { super.onCreate(); refWatcher = LeakCanary.install(this); } }
LeakCanary.install() 返回一个配置好了的 RefWatcher 实例。它同时安装了 ActivityRefWatcher 来监控 Activity 泄漏。即当 Activity.onDestroy() 被调用以后,若是这个 Activity 没有被销毁,logcat 就会打印出信息告诉你内存泄漏发生了。api
leakInfo:In com.example.leakcanary:1.0:1. * com.example.leakcanary.MainActivity has leaked: * GC ROOT thread java.lang.Thread.<Java Local> (named 'AsyncTask #1') * references com.example.leakcanary.MainActivity$2.this$0 (anonymous subclass of android.os.AsyncTask) * leaks com.example.leakcanary.MainActivity instance *
LeakCanary 自动检测 Activity 泄漏只支持 Android ICS 以上版本。由于 Application.registerActivityLifecycleCallbacks() 是在 API 14 引入的。若是要在 ICS 以前监测 Activity 泄漏,能够重载 Activity.onDestroy() 方法,而后在这个方法里调用 RefWatcher.watch(this) 来实现。缓存
public abstract class BaseFragment extends Fragment { @Override public void onDestroy() { super.onDestroy(); RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity()); refWatcher.watch(this); } }
当 Fragment.onDestroy() 被调用以后,若是这个 fragment 实例没有被销毁,那么就会从 logcat 里看到相应的泄漏信息。app
RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity()); refWatcher.watch(someObjNeedGced);
当 someObjNeedGced 还在内存中时,就会在 logcat 里看到内存泄漏的提示。dom
1. RefWatcher.watch() 建立一个 KeyedWeakReference 到要被监控的对象。
2. 而后在后台线程检查引用是否被清除,若是没有,调用GC。
3. 若是引用仍是未被清除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。
4. 在另一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用 另外一个开源库HAHA 解析这个hprof文件。(MAT也能够解析hprof,可是是图形化的形式,HAHA彻底是非UI形式解析)
5. 得益于惟一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄露。
6. HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并肯定是不是泄露。若是是的话,创建致使泄露的引用链。
7. 引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展现出来。ide
如今就来经过代码来说解吧
首先,Application的onCreate方法中调用LeakCanary.install(this);
作初始化工做
LeakCanary.java工具
public static RefWatcher install(Application application) { return install(application, DisplayLeakService.class, AndroidExcludedRefs.createAppDefaults().build()); } public static RefWatcher install(Application application, Class<? extends AbstractAnalysisResultService> listenerServiceClass, ExcludedRefs excludedRefs) { //首先判断当前应用程序进程是否在HeapAnalyzerService所在的进程,若是是直接return,HeapAnalyzerService所在的进程是HAHA库专门解析hprof文件的进程 if (isInAnalyzerProcess(application)) { return RefWatcher.DISABLED; } enableDisplayLeakActivity(application); HeapDump.Listener heapDumpListener = new ServiceHeapDumpListener(application, listenerServiceClass); //listenerServiceClass就是上面的DisplayLeakService.class,用来显示内存泄漏信息用的 RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs); //构造RefWatcher类对象 ActivityRefWatcher.installOnIcsPlus(application, refWatcher); //调用ActivityRefWatcher的方法 return refWatcher; } //ActivityRefWatcher类,专门针对activity,因此在api14以上的onDestroy中不用监控,自动回调 @TargetApi(ICE_CREAM_SANDWICH) public final class ActivityRefWatcher { private final Application.ActivityLifecycleCallbacks lifecycleCallbacks = new Application.ActivityLifecycleCallbacks() { .............. @Override public void onActivityDestroyed(Activity activity) { ActivityRefWatcher.this.onActivityDestroyed(activity); //当activity退出执行了onDestory方法的时候就会执行该onActivityDestroyed方法 } }; private final Application application; private final RefWatcher refWatcher; void onActivityDestroyed(Activity activity) { refWatcher.watch(activity); //调用watch方法 } public void watchActivities() { // Make sure you don't get installed twice. stopWatchingActivities(); application.registerActivityLifecycleCallbacks(lifecycleCallbacks); //api14才引入的方法,任何activity生命周期的变化都会回调给lifecycleCallbacks } public void stopWatchingActivities() { application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks); } }
excludedRefs
/* References that should be ignored when analyzing this heap dump. /
SDK致使的内存泄露,随着时间的推移,不少SDK 和厂商 ROM 中的内存泄露问题已经被尽快修复了。可是,当这样的问题发生时,通常的开发者能作的事情颇有限。LeakCanary有一个已知问题的忽略列表,AndroidExcludedRefs.java
中能够查看到
就是说若是发现AndroidExcludedRefs.java
类中维护的列表类的内存泄漏,那么在DisplayLeakService并不会显示出来,同时HeapAnalyzer在计算到GC roots的最短强引用路径,也会忽略这些类
下面具体分析。
此时初始化完毕,若是退出一个activity那么就会执行refWatcher.watch(activity);
RefWatcher.java
public void watch(Object watchedReference, String referenceName) { checkNotNull(watchedReference, "watchedReference"); checkNotNull(referenceName, "referenceName"); if (debuggerControl.isDebuggerAttached()) { //连上数据线就此时就返回true了 return; } final long watchStartNanoTime = System.nanoTime(); String key = UUID.randomUUID().toString(); //惟一的key retainedKeys.add(key); //key保存在retainedKeys集合 final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue); //虚引用KeyedWeakReference watchExecutor.execute(new Runnable() { //watchExecutor就不说了,自定义的一个Executor @Override public void run() { ensureGone(reference, watchStartNanoTime); //关键ensureGone方法 } }); } void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) { long gcStartNanoTime = System.nanoTime(); long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); removeWeaklyReachableReferences(); if (gone(reference) || debuggerControl.isDebuggerAttached()) { //判断reference是否被回收了已经为null,同时是否插了USB线,调试模式下,不去检查内存泄漏 return; } gcTrigger.runGc(); //触发GC removeWeaklyReachableReferences(); if (!gone(reference)) { //哈,此时说明强制发生GC,该引用还在,那么就没被回收咯,可能发生内存泄漏,因此才能继续往下走 long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); File heapDumpFile = heapDumper.dumpHeap(); //关键,生成dropf文件 if (heapDumpFile == HeapDumper.NO_DUMP) { //NO_DUMP类型,木有生成dropf吧,直接return // Could not dump the heap, abort. return; } long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); heapdumpListener.analyze( new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs)); //heapdumpListener就是上面初始化的ServiceHeapDumpListener } } private boolean gone(KeyedWeakReference reference) { //當referent被回收了,因為removeWeaklyReachableReferences方法因此該key被移出了,contains返回false,gone返回true //當referent沒被回收了,因為key還在retainedKeys中,contains返回true,gone返回false 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; //ReferenceQueue的poll方法,當referent被回收了那麼poll方法返回不為null,沒被GC回收才為null while ((ref = (KeyedWeakReference) queue.poll()) != null) { retainedKeys.remove(ref.key); //當referent被回收了,那麼從retainedKeys移出該key } }
先稍微看下KeyedWeakReference的定義,繼承自WeakReference
final class KeyedWeakReference extends WeakReference<Object> { public final String key; public final String name; KeyedWeakReference(Object referent, String key, String name, ReferenceQueue<Object> referenceQueue) { super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue")); this.key = checkNotNull(key, "key"); this.name = checkNotNull(name, "name"); } }
測試一下
public static void main(String[] args) { // TODO Auto-generated method stub WeakReference w = new WeakReference(new String("haha"), queue); System.gc(); if (w.get() == null) { System.out.println("w get is null"); } else { System.out.println("w get is not null"); } WeakReference temp= (WeakReference) queue.poll(); if(temp != null) { // ReferenceQueue的poll方法 System.out.println("queue.poll is not null"); }else{ System.out.println("queue.poll is null"); } }
打印
w get is null
queue.poll is not null
註釋掉System.gc();
此時打印
w get is not null
queue.poll is null
然後看看heapDumper.dumpHeap()
怎么生成hprof文件,而后再看heapdumpListener.analyze流程
AndroidHeapDumper.java
@Override public File dumpHeap() { if (!leakDirectoryProvider.isLeakStorageWritable()) { CanaryLog.d("Could not write to leak storage to dump heap."); leakDirectoryProvider.requestWritePermission(); return NO_DUMP; } File heapDumpFile = getHeapDumpFile(); // Atomic way to check for existence & create the file if it doesn't exist. // Prevents several processes in the same app to attempt a heapdump at the same time. boolean fileCreated; try { fileCreated = heapDumpFile.createNewFile(); } catch (IOException e) { cleanup(); CanaryLog.d(e, "Could not check if heap dump file exists"); return NO_DUMP; } if (!fileCreated) { CanaryLog.d("Could not dump heap, previous analysis still is in progress."); // Heap analysis in progress, let's not put too much pressure on the device. return NO_DUMP; } FutureResult<Toast> waitingForToast = new FutureResult<>(); //在生成heap dump文件的时候会弹出一个toast提示界面 showToast(waitingForToast); if (!waitingForToast.wait(5, SECONDS)) { CanaryLog.d("Did not dump heap, too much time waiting for Toast."); return NO_DUMP; } Toast toast = waitingForToast.get(); try { Debug.dumpHprofData(heapDumpFile.getAbsolutePath()); //Debug.dumpHprofData方法生成hprof并写入该file,其实DDMS获取hprof文件应该也是调用该方法的 cancelToast(toast); return heapDumpFile; } catch (Exception e) { cleanup(); CanaryLog.d(e, "Could not perform heap dump"); // Abort heap dump return NO_DUMP; } }
hprof文件默认放在/sdcard/Download/…/目录下,因此若是要使用MAT分析的话,能够到该目录下寻找,该hprof不能直接被MAT查看,须要android提供的hprof-conv转换
生成了hprof文件以后,执行heapdumpListener.analyze流程,主要就是经过另一个进程中的HeapAnalyzerService有一个HeapAnalyzer(内部实际调用开源库HAHA)解析这个hprof文件,而后若是须要就显示在传递到APP进程中的DisplayLeakService,并以通知的形式展现出来,代码以下
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass); public final class HeapAnalyzerService extends IntentService { public static void runAnalysis(Context context, HeapDump heapDump, Class<? extends AbstractAnalysisResultService> listenerServiceClass) { //静态方法 Intent intent = new Intent(context, HeapAnalyzerService.class); //启动本服务,此时HeapAnalyzerService运行在leakcanary独立进程中 intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName()); //HeapAnalyzerService intent.putExtra(HEAPDUMP_EXTRA, heapDump); //hropf文件 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); //DisplayLeakService HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA); //hropf文件 HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs); //HeapAnalyzer.checkForLeak解析这个hprof文件完毕,那么发送给app进程的HeapAnalyzerService显示通知 AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey); AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result); } } <service android:name="com.squareup.leakcanary.internal.HeapAnalyzerService" android:enabled="false" android:process=":leakcanary" />
分析hprof文件,设计IO操做,放在leakcanary独立进程固然更好啦,此时没有不是单独开线程的方式
HeapAnalyzer.java
public final class HeapAnalyzer { private final ExcludedRefs excludedRefs; public HeapAnalyzer(ExcludedRefs excludedRefs) { this.excludedRefs = excludedRefs; } public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) { .......... try { //开始解析hropf,HAHA库中的类HprofBuffer,HprofParser,Snapshot HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile); HprofParser parser = new HprofParser(buffer); Snapshot snapshot = parser.parse(); Instance leakingRef = findLeakingReference(referenceKey, snapshot); //findLeakingReference检查该对象是否 // False alarm, weak reference was cleared in between key check and heap dump. if (leakingRef == null) { //为null说明已经被GC回收啦,那么直接return noLeak类型,DisplayLeakService就不显示啦 return noLeak(since(analysisStartNanoTime)); } return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef); //走到这里,就是计算到GC roots的最短强引用路径,并肯定是不是泄露。若是是的话,创建致使泄露的引用链 } catch (Throwable e) { return failure(e, since(analysisStartNanoTime)); } } private Instance findLeakingReference(String key, Snapshot snapshot) { ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName()); //HAHA类库snapshot的findclass方法 List<String> keysFound = new ArrayList<>(); for (Instance instance : refClass.getInstancesList()) { List<ClassInstance.FieldValue> values = classInstanceValues(instance); String keyCandidate = asString(fieldValue(values, "key")); if (keyCandidate.equals(key)) { return fieldValue(values, "referent"); } keysFound.add(keyCandidate); } throw new IllegalStateException( "Could not find weak reference with key " + key + " in " + keysFound); } //计算到GC roots的最短强引用路径,并肯定是不是泄露。若是是的话,创建致使泄露的引用链 private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot, Instance leakingRef) { ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs); //到GCroot最短路劲ShortestPathFinder,此时排除了SDK自带问题的excludedRefs ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef); // False alarm, no strong reference path to GC Roots. if (result.leakingNode == null) { return noLeak(since(analysisStartNanoTime)); } LeakTrace leakTrace = buildLeakTrace(result.leakingNode); //buildLeakTrace方法创建致使泄露的引用链 String className = leakingRef.getClassObj().getClassName(); // Side effect: computes retained size. snapshot.computeDominators(); Instance leakingInstance = result.leakingNode.instance; long retainedSize = leakingInstance.getTotalRetainedSize(); retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance); return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize, since(analysisStartNanoTime)); } private LeakTrace buildLeakTrace(LeakNode leakingNode) //创建致使泄露的引用链 private LeakTraceElement buildLeakElement(LeakNode node) //创建致使泄露的引用链元素
最后来看下app进程中的DisplayLeakService用来显示内存泄漏的引用链的IntentService
public class DisplayLeakService extends AbstractAnalysisResultService { @Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) { String leakInfo = leakInfo(this, heapDump, result, true); CanaryLog.d(leakInfo); boolean resultSaved = false; boolean shouldSaveResult = result.leakFound || result.failure != null; //leakFound表示木有内存泄漏 if (shouldSaveResult) { heapDump = renameHeapdump(heapDump); resultSaved = saveResult(heapDump, result); } PendingIntent pendingIntent; String contentTitle; String contentText; if (!shouldSaveResult) { contentTitle = getString(R.string.leak_canary_no_leak_title); contentText = getString(R.string.leak_canary_no_leak_text); pendingIntent = null; } else if (resultSaved) { pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey); if (result.failure == null) { String size = formatShortFileSize(this, result.retainedHeapSize); String className = classSimpleName(result.className); if (result.excludedLeak) { //注意这里的excludedLeak contentTitle = getString(R.string.leak_canary_leak_excluded, className, size); } else { contentTitle = getString(R.string.leak_canary_class_has_leaked, className, size); } } else { contentTitle = getString(R.string.leak_canary_analysis_failed); } contentText = getString(R.string.leak_canary_notification_message); } else { contentTitle = getString(R.string.leak_canary_could_not_save_title); contentText = getString(R.string.leak_canary_could_not_save_text); pendingIntent = null; } showNotification(this, contentTitle, contentText, pendingIntent); afterDefaultHandling(heapDump, result, leakInfo); } ..... }
第一行的leakInfo打印结果:
leakInfo:In com.example.leakcanary:1.0:1. * com.example.leakcanary.MainActivity has leaked: * GC ROOT thread java.lang.Thread.<Java Local> (named 'AsyncTask #1') * references com.example.leakcanary.MainActivity$2.this$0 (anonymous subclass of android.os.AsyncTask) * leaks com.example.leakcanary.MainActivity instance
至此分析完毕
Debug.dumpHprofData方法生成hprof不能直接在MAT中查看,须要使用android提供的hprof-conv转换
hprof-conv xxxxx.hprof yyyyy.hprof,其中xxxxx.hprof为原始文件,yyyyy.hprof为转换事后的文件
参考:根搜索算法
在主流的商用程序语言中(Java和C#,甚至包括前面提到的古老的Lisp),都是使用根搜索算法(GC Roots Tracing)断定对象是否存活的。这个算法的基本思路就是经过一系列的名为“GC Roots”的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来讲就是从GC Roots到这个对象不可达)时,则证实此对象是不可用的。如图3-1所示,对象object 五、object 六、object 7虽然互相有关联,可是它们到GC Roots是不可达的,因此它们将会被断定为是可回收的对象。
在Java语言里,可做为GC Roots的对象包括下面几种:
1. 虚拟机栈(栈帧中的本地变量表)中的引用的对象。
2. 方法区中的类静态属性引用的对象。
3. 方法区中的常量引用的对象。
4. 本地方法栈中JNI(即通常说的Native方法)的引用的对象。
不管是经过引用计数算法判断对象的引用数量,仍是经过根搜索算法判断对象的引用链是否可达,断定对象是否存活都与“引用”有关。在JDK 1.2以前,Java中的引用的定义很传统:若是reference类型的数据中存储的数值表明的是另一块内存的起始地址,就称这块内存表明着一个引用。这种定义很纯粹,可是太过狭隘,一个对象在这种定义下只有被引用或者没有被引用两种状态,对于如何描述一些“食之无味,弃之惋惜”的对象就显得无能为力。咱们但愿能描述这样一类对象:当内存空间还足够时,则能保留在内存之中;若是内存在进行垃圾收集后仍是很是紧张,则能够抛弃这些对象。不少系统的缓存功能都符合这样的应用场景。
在JDK 1.2以后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)四种,这四种引用强度依次逐渐减弱。
1. 强引用就是指在程序代码之中广泛存在的,相似“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
2. 软引用用来描述一些还有用,但并不是必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常以前,将会把这些对象列进回收范围之中并进行第二次回收。若是此次回收仍是没有足够的内存,才会抛出内存溢出异常。在JDK 1.2以后,提供了SoftReference类来实现软引用。
3. 弱引用也是用来描述非必需对象的,可是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生以前。当垃圾收集器工做时,不管当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2以后,提供了WeakReference类来实现弱引用。
4. 虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,彻底不会对其生存时间构成影响,也没法经过虚引用来取得一个对象实例。为一个对象设置虚引用关联的惟一目的就是但愿能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2以后,提供了PhantomReference类来实现虚引用。
引用计数法是惟一没有使用根集的垃圾回收的法,该算法使用引用计数器来区分存活对象和再也不使用的对象。通常来讲,堆中的每一个对象对应一个引用计数器。当每一次建立一个对象并赋给一个变量时,引用计数器置为1。当对象被赋给任意变量时,引用计数器每次加1当对象出了做用域后(该对象丢弃再也不使用),引用计数器减1,一旦引用计数器为0,对象就知足了垃圾收集的条件。 基于引用计数器的垃圾收集器运行较快,不会长时间中断程序执行,适宜地必须 实时运行的程序。但引用计数器增长了程序执行的开销,由于每次对象赋给新的变量,计数器加1,而每次现有对象出了做用域生,计数器减1。 ps:用根集的方法(既有向图的方法)进行内存对象管理,能够消除循环引用的问题.就是说若是有三个对象相互引用,只要他们和根集是不可达的,gc也是能够回收他们.根集的方法精度很高,可是效率低.计数器法精度低(没法处理循环引用),可是执行效率高.