LeakCanary 内存泄漏 监测 性能优化 简介 原理

GitHub:https://github.com/square/leakcanary

Demo地址:https://github.com/baiqiantao/LeakCanaryTest.git


目录

目录
简单使用
更多介绍
自定义 LeakCanary
测试案例
Application
MainActivity
静态成员导致的内存泄漏
单例导致的内存泄漏

简单使用

A memory leak detection 内存泄露检测 library for Android and Java.

A small leak will sink a great ship. -- Benjamin Franklin

千里之堤, 毁于蚁穴。 -- 《韩非子·喻老》

添加依赖:

dependencies {
  debugImplementation ‘com.squareup.leakcanary:leakcanary-android:1.6.1‘
  debugImplementation ‘com.squareup.leakcanary:leakcanary-support-fragment:1.6.1‘ //当使用support库时添加
  releaseImplementation ‘com.squareup.leakcanary:leakcanary-android-no-op:1.6.1‘ //发布时用的是无任何操作的版本
}

初始化:

LeakCanary.install(application);

配置完以后,在 debug 构建的版本中,如果检测到某个 activity 或 fragment 有内存泄露,LeakCanary 就会自动地显示一个通知。

更多介绍

  • 为什么要使用LeakCanary

    • 内存泄漏是一种编程错误[programming error],会导致应用程序保留对不再需要的对象的引用。因此就会导致无法回收为该对象分配[allocated]的内存,最终导致 OutOfMemoryError crash。
    • 例如,Android Activity 实例在调用 onDestroy 方法后就不再需要了,但是如果在静态字段中存储了对该Activity的强引用将会阻止其被GC[garbage collected]
    • LeakCanary对一个 longer needed 的对象做了唯一标识,并找到阻止它被垃圾回收的引用链。
    • 当作者首次在Square公司的某款App中启用 LeakCanary 后,他找到并修复了多个内存泄漏,并将 OutOfMemoryError 的崩溃率降低了94%。
  • LeakCanary是怎么工作的
    • 通过 RefWatcher.watch() 创建了一个KeyedWeakReference to the watched object。
    • 然后在后台线程检查引用是否被清除了,如果没有,则triggers a GC
    • 如果引用还是未被清除,则 dumps the heap 到文件系统中的 .hprof 文件中。
    • 在另外一个独立的进程中启动 HeapAnalyzerServiceHeapAnalyzer 使用 HAHA 解析 heap dump 。
    • 得益于唯一的 reference key, HeapAnalyzer 在 heap dump 中找到 KeyedWeakReference,并且定位 leaking reference。
    • HeapAnalyzer 计算到 GC roots 的最短强引用路径,并确定是否有泄露。如果有的话,创建导致泄露的引用链。
    • 计算结果传递到 APP 进程中的 DisplayLeakService 中, 并以通知的形式展示出来。
  • 如何修复内存泄漏

    要修复某个内存泄漏,您需要查看该链并查找导致泄漏的那个引用,即在泄漏时哪个引用本应该被清除的。LeakCanary以红色下划线突出显示可能导致泄漏的引用。

  • 如何复制 leak trace 信息

    可以通过 logcat 或通过 Leaks App 的菜单复制·

  • Android SDK可能导致泄漏吗

    是。 在AOSP以及制造商实现中,已经存在许多已知的内存泄漏。 当发生这样的泄漏时,作为应用程序开发人员,您几乎无法解决此问题。

    出于这个原因,LeakCanary 有一个内置的已知Android漏洞列表可供忽略:AndroidExcludedRefs.java。

  • 如何通过 leak trace 挖掘泄漏信息

    有时 leak trace 是不够的,您需要使用 MATYourKit 挖掘 heap dump。 以下是在堆转储中找到泄漏实例的方法:

    • 查找 com.squareup.leakcanary.KeyedWeakReference 的所有实例
    • 对于其中的每一个,请查看 key 字段。
    • 找到 key 字段等于 LeakCanary 报告的 the reference key 的 KeyedWeakReference
    • 找到的那个 KeyedWeakReference 的 referent 字段就是您内存泄漏的对象。
    • 此后,问题就掌握在你手中。A good start 是查看 GC Roots的最短路径(除了弱引用)。
  • 如何修复构建错误
    • 如果leakcan-android不在Android Studio的 external libraries 列表中,但是 leakcanary-analyzer 和 leakcanary-watcher 却存在在那里:尝试做一个 Clean Build。 如果仍然存在问题,请尝试通过命令行构建。
    • error: package com.squareup.leakcanary does not exist: 如果您有其他 build types 而不是 debug 和 release,则还需要为这些构建类型添加特定的依赖项(xxxCompile)。
  • LeakCanary添加了多少个方法
    • 如果您使用ProGuard,答案为9或0。
    • LeakCanary 只应在调试版本中使用,并一定要在发布版本中禁用。我们为您的发布版本提供了一个特殊的空依赖项:leakcanary-android-no-op
    • LeakCanary 的完整版本更大,绝不应在您的 release 版本中发布。
  • 谁在推动 LeakCanary

    LeakCanary由 @pyricau创建并开源,目前由@jrodbx@JakeWharton@pyricau维护。

  • 为什么叫 LeakCanary

    LeakCanary这个名称是参考 煤矿中的金丝雀[canary in a coal mine],因为LeakCanary是一个用于通过提前预警危险[advance warning of a danger]来检测风险[detect risks]的哨兵[sentinel]

  • Instant Run可能触发无效的 leaks

    启用Android Studio的即时运行功能可能会导致LeakCanary报告无效的内存泄漏。 请参阅 Android Issue Tracker 上的问题#37967114(https://issuetracker.google.com/issues/37967114)。

  • 我知道我有泄漏,为什么通知不显示

    你是否 attached to a debugger? LeakCanary在调试时忽略泄漏检测以避免误报。

自定义 LeakCanary

  • 如何观察具有生命周期的对象

    在您的应用程序中,您可能有其他具有生命周期的对象,例如Fragment,Service,Dagger组件等。可以使用RefWatcher来监视应该进行垃圾回收的实例:refWatcher.watch(schrodingerCat);

  • 使用 no-op 依赖

    release 版本的leakcanary-android-no-op依赖项仅包含LeakCanary和RefWatcher类。如果您要自定义LeakCanary,您需要确保自定义仅出现在 debug 版本中,因为它可能会引用leakcanary-android-no-op依赖项中不存在的类。

  • 自定义图标和标签

    DisplayLeakActivity附带了一个默认图标和标签,您可以通过在应用中提供R.mipmap.leak_canary_iconR.string.leak_canary_display_activity_label来更改它:

  • install 方法的默认逻辑
public static RefWatcher install(Application application) {
   return refWatcher(application)
      .listenerServiceClass(DisplayLeakService.class)
      .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
      .buildAndInstall();
}
  • 不监测特定的Activity

    默认情况下,LeakCanary会监视所有的Activity。 您可以自定义 installation steps 以执行不同的操作,例如忽略某种类型Activity的泄漏:

RefWatcher refWatcher = LeakCanary.refWatcher(this)
      .watchActivities(false)
      .buildAndInstall();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
   @Override
   public void onActivityDestroyed(Activity activity) {
      if (activity instanceof IgnoreActivity) {
         return;
      }
      refWatcher.watch(activity);
   }
   //...
});
  • 在运行时打开和关闭 LeakCanary
refWatcher = LeakCanary.refWatcher(this)
      .heapDumper(getHeapDumper()) //在运行时开启和关闭LeakCanary
      .buildAndInstall();
public TogglableHeapDumper getHeapDumper() {
 if (heapDumper == null) {
  LeakDirectoryProvider leakDirectoryProvider = LeakCanaryInternals.getLeakDirectoryProvider(this);
  AndroidHeapDumper defaultDumper = new AndroidHeapDumper(this, leakDirectoryProvider);
  heapDumper = new TogglableHeapDumper(defaultDumper);
 }
 return heapDumper;
}
public class TogglableHeapDumper implements HeapDumper {
 private final HeapDumper defaultDumper;
 private boolean enabled = true;

 public TogglableHeapDumper(HeapDumper defaultDumper) {
  this.defaultDumper = defaultDumper;
 }

 public void setEnabled(boolean enabled) {
  this.enabled = enabled;
 }

 @Override
 public File dumpHeap() {
  return enabled ? defaultDumper.dumpHeap() : HeapDumper.RETRY_LATER;
 }
}
MyApplication.app().getHeapDumper().setEnabled(false);

测试案例

Application

public class MyApplication extends Application {
 private RefWatcher refWatcher;
 private static MyApplication app;
 private TogglableHeapDumper heapDumper;

 @Override
 public void onCreate() {
  super.onCreate();
  if (LeakCanary.isInAnalyzerProcess(this)) {
   Log.i("bqt", "此进程是专用于LeakCanary进行堆分析用的。您不应该在此进程中初始化您的应用。");
   return;
  }

  refWatcher = LeakCanary.refWatcher(this)
    .watchActivities(true) //默认为true,会监视所有Activity,你可以设置为false然后再指定要监测的Activity
    .watchFragments(true) //默认为true,会监视 native Fragment,如果添加了support依赖,则也会监视support中的Fragment
    .watchDelay(1, TimeUnit.SECONDS) //设置应该等待多长时间,直到它检查跟踪对象是否已被垃圾回收
    .maxStoredHeapDumps(7) //设置LeakCanary最多可以保存的 heap dumps 个数,默认为7
    .excludedRefs(getExcludedRefs()) //忽略特定的引用,这个垃圾东西设置后总是不生效
    .heapDumper(getHeapDumper()) //在运行时开启和关闭LeakCanary
    //.listenerServiceClass() //可以更改默认行为以将 leak trace 和 heap dump 上载到您选择的服务器。
    .buildAndInstall();
  app = this;
 }

 private ExcludedRefs getExcludedRefs() {
  return AndroidExcludedRefs.createAppDefaults()//经过大量测试,我感觉TMD完全忽略不了Activity和Fragment中内存泄漏
    .instanceField("com.bqt.test.Single", "imageView") //类名,字段名
    .staticField("com.bqt.test.StaticLeakActivity", "bitmap") //类名,静态字段名
    .clazz("com.bqt.test.StaticLeakActivity") //忽略提供的类名的所有子类的所有字段和静态字段
    .thread("Thread-10086") //忽略指定的线程,一般主线程名为【main】,子线程名为【Thread-整数】
    .build(); //忽略的引用如果又通过watch手动监测了,则仍会监测其内存泄漏情况
 }

 public static MyApplication app() {
  return app;
 }

 public RefWatcher getRefWatcher() {
  return refWatcher;
 }

 public TogglableHeapDumper getHeapDumper() {
  if (heapDumper == null) {
   LeakDirectoryProvider leakDirectoryProvider = LeakCanaryInternals.getLeakDirectoryProvider(this);
   AndroidHeapDumper defaultDumper = new AndroidHeapDumper(this, leakDirectoryProvider);
   heapDumper = new TogglableHeapDumper(defaultDumper);
  }
  return heapDumper;
 }
}

MainActivity

public class MainActivity extends FragmentActivity implements AdapterView.OnItemClickListener {
 private FrameLayout frameLayout;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  ListView listView = new ListView(this);
  String[] array = {"静态成员导致的内存泄漏",
    "单例导致的内存泄漏:Fragment",
    "禁用 LeakCanary",
    "",};
  listView.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array)));
  listView.setOnItemClickListener(this);
  frameLayout = new FrameLayout(this);
  frameLayout.setId(R.id.fragment_id);
  listView.addFooterView(frameLayout);
  setContentView(listView);
 }

 @Override
 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  switch (position) {
   case 0:
    startActivity(new Intent(this, StaticLeakActivity.class));
    break;
   case 1:
    getSupportFragmentManager().beginTransaction()
      .add(frameLayout.getId(), new SingleLeakFragment(), "SingleLeakFragment")
      .commit();
    break;
   case 2:
    MyApplication.app().getHeapDumper().setEnabled(false);
    break;
   default:
    break;
  }
 }
}

静态成员导致的内存泄漏

public class StaticLeakActivity extends Activity {
 private static Bitmap bitmap;

 @Override
 protected void onCreate(@Nullable Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  ImageView imageView = new ImageView(this);
  bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
  imageView.setImageBitmap(bitmap);
  setContentView(imageView);
 }
}

相关信息:

* com.bqt.test.StaticLeakActivity has leaked:
* InputMethodManager$ControlledInputConnectionWrapper.!(mParentInputMethodManager)!
* ? InputMethodManager.!(mLastSrvView)!
* ? PhoneWindow$DecorView.mContext
* ? StaticLeakActivity
* Reference Key: 7f96d2f1-bf17-47e2-84ad-cd5976d72766
* Device: HUAWEI HONOR PLK-UL00 PLK-UL00
* Android Version: 6.0 API: 23 LeakCanary: 1.6.1 26145bf
* Durations: watch=1007ms, gc=149ms, heap dump=1840ms, analysis=6567ms

单例导致的内存泄漏

public class SingleLeakFragment extends Fragment {
 private ImageView imageView;

 @Nullable
 @Override
 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
  imageView = new ImageView(getContext());
  return imageView;
 }

 @Override
 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
  super.onViewCreated(view, savedInstanceState);
  imageView.setImageResource(R.drawable.icon);
  Single.SINGLETON.setImageView(imageView);//单例中引用View同样会导致Activity内存泄漏
 }
}
public enum Single {
 @SuppressLint("StaticFieldLeak")
 SINGLETON; //定义一个枚举的元素,它就代表了Single的一个实例
 private ImageView imageView;

 public void setImageView(ImageView imageView) {
  this.imageView = imageView;
 }
}

相关信息:

* com.bqt.test.SingleLeakFragment has leaked:
* InputMethodManager$ControlledInputConnectionWrapper.!(mParentInputMethodManager)!
* ? InputMethodManager.!(mLastSrvView)!
* ? ListView.mOnItemClickListener
* ? MainActivity.mFragments
* ? FragmentController.mHost
* ? FragmentActivity$HostCallbacks.mFragmentManager
* ? FragmentManagerImpl.mAdded
* ? ArrayList.array
* ? array Object[].[0]
* ? SingleLeakFragment
* Reference Key: 4877bf10-596c-440f-b69c-5d239f670944
* Device: HUAWEI HONOR PLK-UL00 PLK-UL00
* Android Version: 6.0 API: 23 LeakCanary: 1.6.1 26145bf
* Durations: watch=17245ms, gc=138ms, heap dump=1675ms, analysis=8159ms

2018-10-2

原文地址:https://www.cnblogs.com/baiqiantao/p/9736242.html

时间: 2024-10-11 18:47:31

LeakCanary 内存泄漏 监测 性能优化 简介 原理的相关文章

LeakCanary 内存泄露监测原理研究

LeakCanary 内存泄露监测原理研究 LeakCanary源码分析 LeakCanary 原理浅析

C++应用程序性能优化(一)——应用程序性能优化简介

C++应用程序性能优化(一)--应用程序性能优化简介 一.程序性能优化简介 1.程序性能优化简介 在计算机发展的早期阶段,硬件资源相对而言是非常昂贵的,CPU运行时间与内存容量给程序开发人员设置了极大限制.因此,早期的程序对运行性能和内存空间占用的要求是非常严格的,很多开发人员为了减少1%的CPU运行时间,为减少几十个甚至几个字节而不懈努力.随着计算机技术的快速发展,硬件资源变得相对便宜.但如果认为软件开发时,程序的性能优化不再重要,硬件将解决性能问题也是片面的.计算机硬件的发展解决了部分软件的

iOS 内存泄漏监测自动化

在 Android 上,Square 这家公司提供了非常有名的工具: leakcanary ,来帮助开发者们在日常开发过程中就能够发现内存泄漏.但在 iOS 上呢?在 Google 的时候,我发现了两个工具,一个是这篇文章将要翻译并介绍的 Facebook 开源的三件套,另一个则是国内微信阅读团队做的 MLeaksFinder . 关于 MLeaksFinder 这里有两篇其官方提供的文章介绍: MLeaksFinder:精准 iOS 内存泄露检测工具 MLeaksFinder 新特性 简而言之

Android手机内存管理与性能优化

Android手机内存管理与性能优化&JNI.NDK高级编程(JNI.Dalvik.内存监测) 课程分类:Android 适合人群:中级 课时数量:34小节课时 用到技术:Dalvik,DDMS,File Explorer,Adapter和图片处理,查询数据库和Static关键字使用及线程,JNI和NDK等 涉及项目:Android手机内存管理与性能优化,玩转JNI与NDK手机编程 咨询qq:1840215592 Android手机内存管理与性能优化详细介绍:http://www.dwz.cn/

windows内存泄漏监测

之前使用vld检测内存泄露,有兴趣可以一观: http://blog.csdn.net/alex_my/article/details/11488805 控制台下,MFC未测试. 使用方法如下: #include <crtdbg.h>ifdef _DEBUGdefine new new(_NORMAL_BLOCK, __FILE__, __LINE__)endif void EnableMemLeakCheck(){     _CrtSetDbgFlag(_CrtSetDbgFlag(_CRT

Android手机内存管理与性能优化视频教程

课程讲师:xiao_q 课程分类:Android 适合人群:中级 课时数量:34小节 用到技术:Dalvik,DDMS,File Explorer,Adapter和图片处理,查询数据库和Static关键字使用及线程,JNI和NDK等 涉及项目:Android手机内存管理与性能优化,玩转JNI与NDK手机编程 咨询QQ:1609173918 链接:http://pan.baidu.com/s/1i3gnLEt密码:55a0

Facebook 的 iOS 内存泄漏监测自动化实践

内存是移动设备上的共享资源,如果一个 App 无法正确地进行内存管理的话,将会导致内存消耗殆尽,闪退以及性能的严重下降. Facebook 的 iOS 版本的许多功能模块共用了同一份内存空间,如果其中的某一个模块消耗了特别多的内存资源的话,将会对整个 App 造成严重影响.举个栗子,当某个功能模块不小心造成了内存泄漏的时候,这个情况就很有可能会发生. 在 Facebook,我们有非常多的工程师同时在一个代码仓库下进行并行开发.内存泄漏是在开发过程中难以避免会遇见的问题.当内存泄漏发生时,我们就需

Java内存模型及性能优化

最近在做一个项目的性能优化,遇到好多以前没有关注过的性能问题,一头雾水,今天做个笔记,简单记录下JVM相关的参数设置. 一.JVM内存模型 首先介绍下Java程序具体执行的过程: Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.class后缀): 由JVM中的类加载器加载各个类的字节码文件,加载完毕之后,交由JVM执行引擎执行: 在整个程序执行过程中,JVM会用一段空间来存储程序执行期间需要用到的数据和相关信息,这段空间一般被称作为Runtime Data Area(运

OpenCL入门:(三:GPU内存结构和性能优化)

如果我们需要优化kernel程序,我们必须知道一些GPU的底层知识,本文简单介绍一下GPU内存相关和线程调度知识,并且用一个小示例演示如何简单根据内存结构优化. 一.GPU总线寻址和合并内存访问 假设X指向一个32位整数数组的指针,数组首地址是0x00001232,那么一个线程需要访问第0个成员时是也许是如下访问的: int tmp = X[0] 假设内存总线宽度是256位,内存访问时必须和总线宽度对齐,所以内存只能访问0x00000020,0x00000040这种地址(0x20=256位),如