Android—内存泄漏、GC及LeakCanary源码解析
内存抖动:内存频繁的分配和回收,频繁的GC会导致UI卡顿,严重的时候导致OOM。
内存泄露:程序在向系统申请分配内存空间后(new),在使用完毕后未释放。结果导致一直占据该内存单元,我们和程序都无法再使用该内存单元,直到程序结束,这是内存泄露。
内存溢出(OOM):程序向系统申请的内存空间超出了系统能给的。比如内存只能分配一个int类型,我却要塞给他一个long类型,系统就出现oom。又比如一车最多能坐5个人,你却非要塞下10个,车就挤爆了。
大量的内存泄露会导致内存溢出(oom)。
引起内存泄漏的情况
1.对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。
- 比如在Activity中register了一个BraodcastReceiver,但在Activity结束后没有unregister该BraodcastReceiver。
- 资源性对象比如Cursor,Stream、File文件等往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。
- 对于资源性对象在不使用的时候,应该调用它的close()函数将其关闭掉,然后再设置为null。在我们的程序退出时一定要确保我们的资源性对象已经关闭。
2.内部类持有外部类引用,Handler,Runnable匿名内部类导致内存泄露,内部类天然持有外部类的实例引用,activity finish掉了,那么消息队列的消息依旧会由handler进行处理,若此时handler声明为内部类,那么就会导致activity无法回收,进而导activity泄露。
- 在onDestroy方法中调用Handler的removeCallbacksAndMessages方法,清空消息。
- 静态内部类不持有外部类的引用,使用静态的handler不会导致activity的泄露,还要用WeakReference 包裹外部类的对象。弱引用:private WeakReference<Activity类名> weakReference; 创建Handler时传入activity对象。因为我们需要使用外部类的成员,可以通过"activity. "获取变量方法等,如果直接使用强引用,显然会导致activity泄露。
- 将Handler放到抽取出来放入一个单独的顶层类文件中。
- 不要让生命周期长于Activity的对象持有到Activity的引用。
3.静态内部类持有外部成员变量(或context):可以使用弱引用、使用ApplicationContext。
- 用 Activity 或Service的 Context,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了。
- 用 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这将没有任何问题。
- 弱引用:private WeakReference<类名> weakReference; 创建静态内部类对象时传入外部类。
4.集合中没用的对象没有及时remove。
- 我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。
- 在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。
5.WebView
WebView单独存放在一个activity中,onDestory()中调用android.os.Process.killProcess(android.os.Process.myPid())等方法杀死进程。
activity泄漏可以使用LeakCanary。
内存泄露原因分析
在JAVA中JVM的栈记录了方法的调用,每个线程拥有一个栈。在线程的运行过程当中,执行到一个新的方法调用,就在栈中增加一个内存单元,即帧(frame)。在frame中,保存有该方法调用的参数、局部变量和返回地址。然而JAVA中的局部变量只能是基本类型变量(int),或者对象的引用。所以在栈中只存放基本类型变量和对象的引用。引用的对象保存在堆中。
当某方法运行结束时,该方法对应的frame将会从栈中删除,frame中所有局部变量和参数所占有的空间也随之释放。线程回到原方法继续执行,当所有的栈都清空的时候,程序也就随之运行结束。
而对于堆内存,堆存放着普通变量。在JAVA中堆内存不会随着方法的结束而清空,所以在方法中定义了局部变量,在方法结束后变量依然存活在堆中。
综上所述,栈(stack)可以自行清除不用的内存空间。但是如果我们不停的创建新对象,堆(heap)的内存空间就会被消耗尽。所以JAVA引入了垃圾回收(garbage collection,简称GC)去处理堆内存的回收。
1."垃圾的判定"
引用计数法
简单理解就是记录一个对象被引用的次数,一个引用被使用引用计数器就+1,反之就-1,当引用次数为0就说明是一个垃圾对象可以被回收了。
缺点:无法解决相互引用。弃用。
可达性分析法
”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
这个算法的基本思想是通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的。
GC管理的主要区域是Java堆,一般情况下只针对堆进行垃圾回收。方法区、栈和本地方法区不被GC所管理,因而选择这些区域内的对象作为GC roots,被GC roots引用的对象不被GC回收。
- 虚拟机栈中栈桢中的局部变量(也叫局部变量表)中引用的对象
- 方法区中类的静态变量、常量引用的对象
- 本地方法栈中 JNI (Native方法)引用的对象
2.回收算法
标记–整理算法
标记-整理法是标记-清除法的一个改进版。在标记阶段,该算法也将所有对象标记为存活和死亡两种状态;在第二个阶段,没有直接对死亡的对象进行清理,而是将所有存活的对象整理一下,放到另一处空间,以达到整理内存碎片的作用,然后把剩下的所有对象全部清除。
复制算法
该算法将内存平均分成两部分,然后每次只使用其中的一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另一个内存中,然后将之前的内存清空,只使用这部分内存,循环下去。
分代回收算法:
新生代(复制算法):在堆中,一个Eden区和两个Survivor区(From、To),比例为(8:1:1)。初始时新创建的对象会存放在Eden区,经过第一次GC后,将存活对象复制到To区。From则看对象年龄,每GC一次年龄+1,到年龄阈值(默认15岁)时会进入到老年代,没有则复制到To区,此时会清理Eden区,清空From区,然后From和To区交换,既空的变为To区。当To区满的情况,会把所有对象都移到老年代中。
老年代(标记整理法):在堆中,年老代主要存放经过几次GC后的对象,内存大小相对会比较大,垃圾回收也相对没有那么频繁。
永久代:在方法区,放着一些被虚拟机加载的类信息,静态变量,常量等数据。
由于堆内存中对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Minor GC和Full GC
Minor GC
在Eden 和 Survivor 区域回收内存被称为 Minor GC。
触发条件:一般情况下,当新对象生成,并且在Eden区满时,就会触发Minor GC,对Eden区域进行GC, 清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
Full GC
对整个堆进行整理,包括年轻代、年老代和永久代。
触发条件:
- 当Eden区和From区满时
- 老年代空间不足
- 方法区空间不足
- System.gc()被调用
- 由Eden区、From区向To区复制时,对象大小大于To可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。
GC垃圾收集器发展史
JDK1.8元空间代替永久代
元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存:理论上取决于32位/64位系统可虚拟的内存大小,可见也不是无限制的,需要配置参数。
引用类型
从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
1. 强引用(Strong reference)
实际编码中最常见的一种引用类型。常见形式如:A a = new A();等。强引用本身存储在栈内存中,其存储指向对内存中对象的地址。如果一个对象具有强引用,则无论在什么情况下,GC都不会回收被引用的对象。
2. 软引用(Soft Reference)
软引用的一般使用形式如下:
A a = new A();
SoftReference<A> srA = new SoftReference<A>(a);
表示一个对象处在有用但非必须的状态。如果一个对象具有软引用,在内存空间充足时,GC就不会回收该对象;当内存空间不足时,GC会回收该对象的内存(回收发生在OutOfMemoryError之前)。
3. 弱引用(Weak Reference)
同样的,软引用的一般使用形式如下:
A a = new A();
WeakReference<A> wrA = new WeakReference<A>(a);
无论当前内存是否紧缺,GC都将回收被弱引用关联的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被GC回收了,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中,以便在恰当的时候将该弱引用回收。
4. 虚引用(Phantom Reference)
虚引等同于没有引用,这意味着在任何时候都可能被GC回收,设置虚引用的目的是为了被虚引用关联的对象在被垃圾回收器回收时,能够收到一个系统通知。
虚引用在使用时必须和引用队列(ReferenceQueue)联合使用。
ReferenceQueue queue=new ReferenceQueue();
PhantomReference pr=new PhantomReference(object.queue);
LeakCanary
由Square开源的一款轻量第三方内存泄漏检测工具,可以实时监测Activity,并提高内存泄漏信息。
原理:watch一个即将要销毁的对象
使用方法:在Applicantion的子类里。
LeakCanary.install(this);
public static @NonNull RefWatcher install(@NonNull Application application) {return refWatcher(application).listenerServiceClass(DisplayLeakService.class).excludedRefs(AndroidExcludedRefs.createAppDefaults().build()).buildAndInstall();}
内部建立了一个refWatcher对象,通过refWatcher监听activity回收情况。
DisplayService是发生内存泄漏时的通知服务
excludedRefs()是排除Android源码出现的内存泄漏问题
最主要的是AndroidRefWatcherBuilder.buildAndInstall()
进入buildAndInstall()方法。
public @NonNull RefWatcher buildAndInstall() {if (LeakCanaryInternals.installedRefWatcher != null) {throw new UnsupportedOperationException("buildAndInstall() should only be called once.");}RefWatcher refWatcher = build();if (refWatcher != DISABLED) {
// 根据app包名生成LeakCanary关联应用if (enableDisplayLeakActivity) {LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);}
// 监听Activityif (watchActivities) {ActivityRefWatcher.install(context, refWatcher);}
// 监听Fragmentif (watchFragments) {FragmentRefWatcher.Helper.install(context, refWatcher);}}LeakCanaryInternals.installedRefWatcher = refWatcher;return refWatcher;}
ActivityRefWatcher.install(context, refWatcher);
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {Application application = (Application) context.getApplicationContext();ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);// 通过在Application注册监听每个Activity的生命周期,然后转发给RefWatcherapplication.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);}private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =new ActivityLifecycleCallbacksAdapter() {@Override public void onActivityDestroyed(Activity activity) {refWatcher.watch(activity);}};
FragmentRefWatcher.Helper.install(context, refWatcher);
public static void install(Context context, RefWatcher refWatcher) {List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();.......Helper helper = new Helper(fragmentRefWatchers);Application application = (Application) context.getApplicationContext();//给application注册activityLifecycleCallbacks监听application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);}//在onActivityCreated的时候遍历fragmentRefWatchers队列给每个watcher调用watchFragments(activity)方法private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =new ActivityLifecycleCallbacksAdapter() {@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {for (FragmentRefWatcher watcher : fragmentRefWatchers) {watcher.watchFragments(activity); }}};// 到watchFragments(activity)方法中private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =new FragmentManager.FragmentLifecycleCallbacks() {//在Fragment摧毁的时候调用watch方法@Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {View view = fragment.getView();if (view != null) {refWatcher.watch(view);}}@Overridepublic void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {refWatcher.watch(fragment);}};@Override public void watchFragments(Activity activity) {FragmentManager fragmentManager = activity.getFragmentManager();
//watchFragments方法里面又注册了一层监听fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);}
总结:
- 通过 Application.registerActivityLifecycleCallbacks() 监听Activity的生命周期,在onActivityDestroyed阶段调用RefWatcher的watch(Object)方法。
- 通过FragmentManager.registerFragmentLifecycleCallbacks() 监听Fragment的生命周期并在onFragmentViewDestroyed和onFragmentDestroyed阶段调用RefWatcher的watch(Object)方法。
我们得知监听最后都是通过调用RefWatcher的watch(Object)方法,接下来到RefWatcher的watch(Object)方法里面看看。
//watchedReference传入的就是activity或fragment对象public void watch(Object watchedReference, String referenceName) {if (this == DISABLED) {return;}checkNotNull(watchedReference, "watchedReference");checkNotNull(referenceName, "referenceName");final long watchStartNanoTime = System.nanoTime();
//为监听生成唯一IDString key = UUID.randomUUID().toString();
//将key值存入一个队列retainedKeys.add(key);//生成reference对象,封装了watchedReference和key等。
//queue是Reference队列,对象被回收了,那么对应的弱引用对象会在回收时被添加到queue中,
//重点:KeyedWeakReference内部继承了弱引用类,reference是弱引用对象,调用GC时就会被回收。final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
//最重要的是这个方法,传入了监听对象ensureGoneAsync(watchStartNanoTime, reference);}//watchExecutor.execute方法确保了主线程空闲,生命周期走完后才会在子线程运行run方法private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {watchExecutor.execute(new Retryable() {@Override public Retryable.Result run() {return ensureGone(reference, watchStartNanoTime);}});}
接下来看看ensureGone方法的具体实现
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {long gcStartNanoTime = System.nanoTime();long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);// 将queue队列中对象的key从key队列中移除,因为在queue队列的reference已经被回收removeWeaklyReachableReferences();if (debuggerControl.isDebuggerAttached()) {// The debugger can create false leaks.return RETRY;}// 如果key队列没有该reference的key值,说明在某次GC已经回收该对象,没有内存泄漏,不需要处理if (gone(reference)) {return DONE;}// 执行一次GCgcTrigger.runGc();// 再检查removeWeaklyReachableReferences();// key队列还有key值,说明已经内存泄漏,dumpif (!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;}private boolean gone(KeyedWeakReference reference) {return !retainedKeys.contains(reference.key);}
ensureGone()主要将已经GC掉的弱引用对象从队列中移除,然后判断是否已经没有,如果没有跳出,有则会执行GC后再检查,如果有说明内存泄漏,创建dump文件,分析dump文件。
RefWatcher.watch(Object)总结:
RefWatcher.watch(Object)会为每一个监听引用对象(Activity或Fragment)提供唯一ID,RefWatcher类有两个队列分别存放reference对象和key,在主线程走完生命周期后通过 Retryable.run()在子线程检测内存泄漏,监测是ensureGone()方法通过两个队列来判断其是否reference对象是否GC回收。
LeakCanary总结:
LeakCanary调用install方法,内部创建RefWather对象,对象分别设置了对Activity和Fragment在Destroy阶段的监听,就是当Activity和Fragment在Destroy阶段时调用watch方法传入Activity或Fragment对象并生成相对应的唯一值key封装成reference弱引用对象(注意是弱引用对象,因为弱引用对象GC后会被传入Reference队列),最后调用sureGone方法在主线程生命周期走完后,在子线程中进行检查,先将Reference队列的对象的key从key队列中移出,再判断现reference对象的key在不在key队列里,不在则该对象已经被回收,否则再调用一次GC,再判断一次,有内存泄漏便会进行 heap dump 的操作以获取当前内存的 hprof。通过对hprof文件进行解析,计算出GC root的最短引用路径,反馈异常。
Android—内存泄漏、GC及LeakCanary源码解析相关推荐
- LeakCanary源码解析
LeakCanary源码解析 本文我们来看下LeakCanary的源码,以下内容基于com.squareup.leakcanary:leakcanary-android:1.6.3 LeakCanar ...
- Android技术栈--HashMap和ArrayMap源码解析
1 总览 WARNING!!:本文字数较多,内容较为完整并且部分内容难度较大,阅读本文需要较长时间,建议读者分段并耐心阅读. 本文会对 Android 中常用的数据结构进行源码解析,包括 HashMa ...
- Android OpenGL ES 学习(十) – GLSurfaceView 源码解析GL线程以及自定义 EGL
OpenGL 学习教程 Android OpenGL ES 学习(一) – 基本概念 Android OpenGL ES 学习(二) – 图形渲染管线和GLSL Android OpenGL ES 学 ...
- Android经典著名的百大框架源码解析(retrofit、Okhttp、Glide、Zxing、dagger等等)
我们Android程序员每天都要和源码打交道.经过数年的学习,大多数程序员可以"写"代码,或者至少是拷贝并修改代码.而且,我们教授编程的方式强调编写代码的艺术,而不是如何阅读代码. ...
- 安卓开发者必看:Android的数据结构与算法——ArrayList源码解析
作者:JerryloveEmily 原文链接:https://www.jianshu.com/p/159426e2aaf6 文章有点长,比较啰嗦,请耐心看完! 一.概述 首先得明白ArrayList在 ...
- Android的数据结构与算法----ArrayList源码解析
转载请标明出处: http://blog.csdn.net/abren32/article/details/56669369 本文出自JerryloveEmily的博客 文章有点长,比较啰嗦,请耐心看 ...
- Android图案密码,手势锁源码解析
Android图案密码解锁源码解析 Android Lock Pattern 源码解析 1. 介绍 1.1 关于 Android 的图案密码解锁,通过手势连接 3 * 3 的点矩阵绘制图案表示解 ...
- Android 图片加载框架Gilde源码解析
1.使用Gilde显示一张图片 Glide.with(this).load("https://cn.bing.com/sa/simg/hpb/xxx.jpg").into(imag ...
- Android开发神器:OkHttp框架源码解析
前言 HTTP是我们交换数据和媒体流的现代应用网络,有效利用HTTP可以使我们节省带宽和更快地加载数据,Square公司开源的OkHttp网络请求是有效率的HTTP客户端.之前的知识面仅限于框架API ...
最新文章
- 刘知远 陈慧敏:流言止于“智”者——网络虚假信息的特征与检测
- 【Git】Git 本地的撤销修改和删除操作
- 日记 [2007年04月05日]QMAIL服务器回顾
- Python网络爬虫与信息提取(一)(入门篇)
- 论文阅读:Multi-scale orderless pooling of deep convolutional activation features
- 软件开发中IT用语-日文和英文对照版
- nasm汇编:段的申明、$$、$
- orale用户角色管理
- 弹框在一个很的长页面居中显示
- 《spring-boot学习》-12-@controller和@RestController的区别?
- Github 用户喊话微软:放弃 ICE 吧,不然会失去我们的
- python老师 招聘_崩了,Python玩大了! 程序员:牛,不可惜!
- APP开发用什么框架最好?这5大框架,开发者必备神器
- php setlocale 中国,PHP setlocale无效
- C - Matrix Reducing
- iOS-Building for iOS Simulator, but the linked and embedded framework ‘XX.framework‘ was built for
- 3D重建的进阶了解---深度图,网格,体素,点云是什么
- 互联网最新报告出炉!程序员必看系列!
- 统计之 - 离均差平方和
- 汽车CAN总线分析仪
热门文章
- 常见的python模块_python常见模块
- centos 6.5 openldap php,centos6.5安装openldap+phpldapadmin
- 三星 P600 android,顶级硬件S pen笔手写—三星P600_三星 Galaxy Note 10.1 2014 Edition P600_平板电脑市场-中关村在线...
- springboot 跨域_SpringBoot跨域加SpringSecurity就失效
- mongodb 数字 _id_MongoDB学习笔记MongoDB简介及数据类型
- servlet容器_Servlet详解(一)之基本概念
- 17种元器件PCB封装图鉴,美翻了
- Ubantu18.04安装Vivado
- aodv协议C语言代码,AODV协议的运行方式
- python计算单词长度_Python - 按长度打印单词