xml地图|网站地图|网站标签 [设为首页] [加入收藏]

智能家电

当前位置:美高梅游戏网站 > 智能家电 > 开源项目之LeakCanary源码解析

开源项目之LeakCanary源码解析

来源:http://www.gd-chuangmei.com 作者:美高梅游戏网站 时间:2019-09-08 03:06

LeakCanary项目是大名鼎鼎的square公司为Java&Android开发提供的一个自动检测内存泄漏的工具,现在很多项目都在引入来提高代码质量,减少不必要的内存泄漏。尽管Java有垃圾回收机制,但是一些无意识的代码经常会导致对象的引用存在超过其生命周期,比如最常见的Activity泄漏,Message泄漏等,本文通过源码分析来了解它是如何做到的。

前两天,Square开源了一个内存泄露自动探测神器——LeakCanary,它是一个Android和Java的内存泄露检测库,可以大幅度减少了开发中遇到的OOM问题,对于开发者来说,无疑是个福音,下面对该库的readme进行简单的翻译:

public class MainActivity extends Activity {private static Object leakReference;@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); //注册一个Android平台的RefWatcher RefWatcher refWatcher = LeakCanary.androidWatcher(getApplicationContext(), new ServiceHeapDumpListener(getApplicationContext(), DisplayLeakService.class), AndroidExcludedRefs.createAppDefaults); //将显示结果的Activity设置成可用 LeakCanary.enableDisplayLeakActivity(getApplicationContext; Object obj = new Object(); //故意将leakRefrence指向obj,使得这个对象的强引用存在时间超过其生命周期 leakReference = obj; refWatcher.watch; }}

“A small leak will sink a great ship.” - Benjamin Franklin
小漏不补沉大船。——本杰明 富兰克林

这样,我们直接Run app,可以看到通知栏上有内存泄漏的标记:

图片 1

图片 2Paste_Image.png图片 3Paste_Image.png图片 4Paste_Image.png

Getting started

在项目的build.gradle文件添加:

 dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
 }

在Application类添加:

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    LeakCanary.install(this);
  }
}

当在你的debug构建过程中出现内存泄露时,LeakCanary将会自动展示一个通知栏。

点开通知之后可以发现我们这个Object泄漏的路径,是不是很赞?好吧,如果这是你的第一印象,我相信一些爱思考的童鞋马上会反应过来,这个不对吧,为啥要我们自己去调用watch(Object watchedReference)呢?内存泄漏不应该是自动检查的吗?呵呵,这是LeakCanary实现的第一个步骤带来的必然限制,我先卖个关子,嘿嘿。

为什么我应该使用LeakCanary?

问得好!我们正好写了个博客回答这个问题。

总体流程

LeakCanary中,检测主要分为三步:1.检测一个对象是否是可疑的泄漏对象;2.如果第一步发现可疑对象,dump内存快照,通过分析.hprof文件,确定怀疑的对象是否真的泄漏。3.将分析的结果展示

如果你对ReferenceQueue的机制很熟悉的话,可以使用ReferenceQueue对对象的可达性进行监视,最常见的比如JDK中的WeakHashMapLeakCanary同样使用了这个机制。看看RefWatcher.watch方法源码:

public void watch(Object watchedReference, String referenceName) {//如果VM正连接到Debuger,忽略这次检测,因为Debugger可能会持有一些在当前上下文中不可见的对象,导致误判if (debuggerControl.isDebuggerAttached { return;}final long watchStartNanoTime = System.nanoTime();//对一个Refercen产生一个唯一的KeyString key = UUID.randomUUID().toString();//放到key集合中retainedKeys.add;//讲watch传入的对象添加一个弱引用final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);//在异步线程上开始分析这个弱引用watchExecutor.execute(new Runnable() { @Override public void run() { ensureGone(reference, watchStartNanoTime); }});}KeyedWeakReference extends WeakReference<Object> { //对每一个弱引用添加标识key public final String key;}

你看到了watch方法其实并没有什么高深的地方,无非是使用一个弱引用连接到你需要检测的对象,然后使用ReferenceQueue来监测这个弱引用可达性的改变,需要注意的是,这里我们通过UUID类为每一个弱引用添加了一个唯一标识。

Android平台上,LeakCanary为我们简单实现了一个AndroidWatchExecutor,里面封装了一个HandlerThread线程来处理这些分析逻辑(HandlerThreadAndroid SDK提供的封装好了LooperThread)。AndroidWatchExecutor中通过Handler

new Runnable() { @Override public void run() { ensureGone(reference, watchStartNanoTime); }};

提交到线程的消息队列中。我们看看ensureGone里面做了什么。

void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {//清除此时已经到ReferenceQueue中的弱引用removeWeaklyReachableReferences如果当前检测的对象已经弱可达,那么说明对象已经不会泄漏 if (gone(reference) || debuggerControl.isDebuggerAttached { return;}//如果当前检测对象还没有改变其可达状态,GC()gcTrigger.runGc();//再次判断对象有没有进入队列removeWeaklyReachableReferences如果此时还没有检测到入队列,那么有可能这个对象已经泄漏if (!gone(reference)) { //进入第二部,dump内存,分析内存快照 doNextStep();}}private void removeWeaklyReachableReferences() {//清除此时已经到队列中的弱引用KeyedWeakReference ref;while ((ref = (KeyedWeakReference) queue.poll != null) { //在key列表中删除这个key,因为这个key映射到的weakreference已经是弱可达了,那么说明此时必然没有强引用指向需要监测的对象 retainedKeys.remove;}

}

**重点注意:A> 上述代码中标注处,实际上会存在一点小问题,如果此时对象在finalize()把对象复活,那么此时实际上是存在内存泄漏风险的,但是这里会直接放过这次check。B> 为什么还需要doNextStep()呢?因为我们这里调用GC并不能保证JVM真正会执行GC,只是建议。如果没有走GC的话,所谓对象标记也就不会执行,对象的可达性就不会变化,所以这里只能猜测这个对象很有可能泄漏,所以LeakCanary还需要真正看看此时的内存堆上的情况。**

这里我们就可以解释为啥LeakCanary在进行内存泄漏时需要手动调用watch去监测一个对象,因为我们需要一个合适的时机去用一个弱引用把这个对象使用ReferenceQueue监测起来。如此一来,LeakCanary最大的一个缺陷就是我需要知道一个对象的确切生命周期,并在我们认为其生命周期应该结束的时间点进行watch。对于Android平台来说,常见的场景就是Activity、Fragment等对象的onDestory中。

你可能已经发现了,本文第一节使用的代码和官方的Demo中直接在Application中注册不同,那么有啥区别呢?

public static RefWatcher refWatcher;//Application中@Override public void onCreate() { super.onCreate(); refWatcher = LeakCanary.install;}

Application中注册,实际上仅仅是在Application.ActivityLifecycleCallbacks中的onActivityDestroyed为所有的Activity进行watch,由于这个接口是Android 4.0以后才有的,所以对于4.0以前的版本,我们需要在Activity的基类中调用refWatcher.watch

上面的doNextStep()中包含的代码,主要功能是dump当前内存堆的hprof文件,并使用square公司另外一个开源分析内存工具HaHa。

Android dump内存接口:Debug.dumpHprofData(heapDumpFile.getAbsolutePath;

.hprof文件交给一个HeapAnalyzerService处理,注意,这个HeapAnalyzerService是运行在和当前app进程的另外一个进程中。HeapAnalyzerLeakCanaryhprof工具类,分析代码:

public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {try { //加载hprof文件 HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile); HprofParser parser = new HprofParser; Snapshot snapshot = parser.parse(); //找到泄漏对象的引用 Instance leakingRef = findLeakingReference(referenceKey, snapshot); // False alarm, weak reference was cleared in between key check and heap dump. if (leakingRef == null) { return noLeak(since(analysisStartNanoTime)); } //查找从这个对象的引用到GC ROOT的最短路径 return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);} catch (Throwable e) { return failure(e, since(analysisStartNanoTime));}}//找到泄漏的对象的引用private Instance findLeakingReference(String key, Snapshot snapshot) { //找到内存中所有的KeyedWeakReference引用 ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName; List<String> keysFound = new ArrayList<>(); for (Instance instance : refClass.getInstancesList { List<ClassInstance.FieldValue> values = classInstanceValues; String keyCandidate = asString(fieldValue(values, "key")); // 判断是否是持有检测对象的弱引用,这里由于每一个KeyedWeakReference具有唯一的UUID //所以可以根据key来找到 if (keyCandidate.equals { return fieldValue(values, "referent"); } keysFound.add(keyCandidate); } throw new IllegalStateException( "Could not find weak reference with key " + key + " in " + keysFound);}

至此,我们已经找到dump中泄漏对象的引用路径,(从泄漏对象到GC根的最短路径),如果存在这样的路径,那么我们可以肯定这个对象已经泄漏,此时LeakCanary会发一个Notification,并设置一个PenddingIntent到展示的Activity

这里就不多说了,无非就是把泄漏的路径放到Activity中展示一下。

LeakCanary提供了ExcludedRefs来灵活控制是否需要将一些对象排除在考虑之外,因为在Android Framework层自身也存在一些内存泄漏,对于开发者来说这些泄漏是我们无能为力的,所以在AndroidExcludedRefs中定义了很多排除考虑的类。

以上,就是LeakCanary的一些源码分析。

最后补充点感想吧,我之前曾经在公司内部的技术文档上看到了一位同事曾经也提出过可以使用Reference队列来监测Activity的内存泄漏,但是当时有几个技术点没有解决,主要原因是我们进行到第一步之后,没有想到使用dump内存的方式来进行泄漏确认,更没有想到HAHA这个开源项目直接提供了解析hprof文件的功能,还是要为Square公司点赞!

那怎么使用它呢?

使用一个RefWatcher观察引用什么时候应该被GC:

RefWatcher refWatcher = {...};

// We expect schrodingerCat to be gone soon (or not), let's watch it.
refWatcher.watch(schrodingerCat);

LeakCanary.install() 返回一个先前配置的RefWatcher,它也安装一个ActivityRefWatcher以便在Activity.onDestroy()被调用后自动检测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);
  }
}

你可以使用RefWatcher观察Fragment的内存泄露

public abstract class BaseFragment extends Fragment {

  @Override public void onDestroy() {
    super.onDestroy();
    RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
    refWatcher.watch(this);
  }
}

How does it work?

1.RefWatcher.watch()创建一个KeyedWeakReference去检测对象;
2.接着,在后台线程,它将会检查是否有引用在不是GC触发的情况下需要被清除的;
3.如果引用引用仍然没有被清除,将会转储堆到.hprof文件到系统文件中(it them dumps the heap into a .hprof file stored on the app file system.)
4.HeapAnalyzerService是在一个分离的进程中开始的,HeapAnalyzer通过使用HAHA解析heap dump;
5.由于一个特殊的引用key和定位的泄露引用,HeapAnalyzer可以在heap dump中找到KeyedWeakReference;
6.如果有一个泄露,HeapAnalyzer计算到GC Roots的最短的强引用路径,然后创建造成泄露的引用链;
7.结果在app的进程中传回到DisplayLeakService,并展示泄露的通知消息;

本文由美高梅游戏网站发布于智能家电,转载请注明出处:开源项目之LeakCanary源码解析

关键词:

上一篇:已有Android工程集成ReactNative页面

下一篇:没有了