安卓手机内存泄漏问题的简单介绍
内存抖动是啥?内存持续升高是啥?内存泄漏又是啥?内存溢出又是啥?会导致什么问题?哪些问题导致的?如何检测?有哪些解决办法?系统内是否有对应机制?首先,简单介绍一下安卓APP应用的运行原理:为了能够使Android应用程序能够高效快速地运行,所以Android的每一个应用程序都会有一个专有的Davilk虚拟机实例对象来运行,这个Davilk对象是由Zygote服务进程孵化出来的,这样的机制使
内存抖动是啥?内存持续升高是啥?内存泄漏又是啥?内存溢出又是啥?会导致什么问题?哪些问题导致的?如何检测?有哪些解决办法?系统内是否有对应机制?
首先,简单介绍一下安卓APP应用的运行原理:为了能够使Android应用程序能够高效快速地运行,所以Android的每一个应用程序都会有一个专有的Davilk虚拟机实例对象来运行,这个Davilk对象是由Zygote服务进程孵化出来的,这样的机制使每个应用进程都只能在自己的进程空间内运行。简而言之,就是一个APP-》一个Dalvik虚拟机实例-》进程启动-》创建一个UI线程。
再者,介绍一下内存的相关知识:
众所周知的Java是在JVM所虚拟的内存环境中运行的,JVM的内存可分为三个区:栈(stack)、堆(heap)、方法区(method).
1.栈(stack),一种数据结构,学过数据结构的都知道它最显著的特点就是“后进先出”,但栈里面只存放基本类型和对象的引用(不存放对象,只存放对象的引用)
2.堆(heap):堆内存用于存放由new创建的对象和数组。在堆中分配的内存,是由JAVA虚拟机自动垃圾回收管理器来管理。JVM只有一个堆区被所有的线程共享,堆中不存放基本类型和对象引用,只存放对象本身。
3.方法区(method):又叫静态区。被所有线程共享,包含所有的class及static变量。
那对象的引用又有哪些???
对象的引用分为以下四种(按级别划分):
1.强引用(Strong reference):实际编码中最常见的一种引用类型。常见形式如:A a =new A();等。强引用本身存储在栈内存中,其存储指向内存中对象的地址。一般情况下,当对内存中的对象不再有任何强引用指向它时,垃圾回收机制开始考虑可能要对此内存进行垃圾回收。如当进行编码:a=null,此时,刚刚在堆中分配地址并新建的a对象没有其他的任何引用,当系统进行垃圾回收时,堆内存将垃圾回收。
2.软引用(Soft Reference):软引用的一般使用形式如下:
A a=new A();
SoftReference<A> srA=new SoftReference<A>(a);
软引用所指示的对象进行垃圾回收需满足以下条件:
1.当其指示的对象没有任何强引用对象指向它
2.当虚拟机内存不足时。
3.弱引用(WeakReference):弱引用的一般格式:
A a=new A();
WeakReference<A> srA=new WeakReference<A>(a);
WeakReference不改变原有强引用对象的垃圾回收时机,一旦其指示对象没有任何强引用对象时,此对象即进入正常的垃圾回收流程。4.虚引用(Phantom Reference):
回到主题,来介绍一下这几个概念:
内存抖动:大量的对象被创建又在短时间内被释放。如下图(使用Mermory Monitor检测)
内存持续升高:APP在运行的过程中占用的内存值持续升高。当其内存升高到超出内存所给的,就会造成内存溢出。类似如下图
内存泄漏:指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。会造成程序卡顿甚至崩溃。如果持有对象的强引用,垃圾回收机制是无法在内存中回收这个对象的。
内存溢出(OOM):程序向系统申请的内存空间超出了系统能给的。PS:大量的内存泄漏会导致内存溢出进而导致程序崩溃。
造成内存泄漏的主要原因是:持有对象的强引用且并没有及时释放,进而造成内存单元一直被占用,浪费内存空间。
垃圾回收机制:垃圾回收(garbage collection,简称GC)可以自动清空堆中不再使用的对象。在JAVA中对象是通过引用使用的。如果再没有引用指向该对象,那么该对象就无从处理或调用该对象,这样的对象称为不可到达(unreachable)。垃圾回收用于释放不可到达的对象所占据的内存。
那排查内存泄漏的一般步骤有哪些???
排查内存泄漏步骤:
通过统计平台了解OOM情况->重现问题->在发生内存泄漏时Dump内存->在内存分析工具中反复查看,找到原本该被回收掉的对象->计算此对象到GC roots的最短强引用路径->确定并修复问题
来介绍一下几种常见的造成内存泄漏的情况及对应的解决办法:单例造成的内存泄漏:
单例的静态特性导致其生命周期同应用一样长。有时创建单例时如果我们需要Context对象,如果传入的是Application的Context那么不会有问题。如果传入的是Activity的Context对象,那么当Activity生命周期结束时,该Activity的引用依然被单例持有,所以不会被回收,而单例的生命周期又是跟应用一样长,所以这就造成了内存泄露。
非静态内部类创建静态实例造成的内存泄漏:
在 Java 中,非静态内部类(包括匿名内部类,比如 Handler, Runnable匿名内部类最容易导致内存泄露)会持有外部类对象的强引用(如 Activity),而静态的内部类则不会引用外部类对象。非静态内部类或匿名类因为持有外部类的引用,所以可以访问外部类的资源属性成员变量等;静态内部类不行。因为普通内部类或匿名类依赖外部类,所以必须先创建外部类,再创建普通内部类或匿名类;而静态内部类随时都可以在其他外部类中创建。
Handler造成的内存泄漏:
在Android开发中,我们经常会使用Handler来控制主线程UI程序的界面变化,使用非常简单方便,但是稍不注意,很容易引发内存泄漏。我们知道,Handler、Message、MessageQueue是相互关联在一起的,Handler通过发送消息Message与主线程进行交互,如果Handler发送的消息Message尚未被处理,该Message及发送它的Handler对象将被MessageQueue一直持有,这样就可能会导致Handler无法被回收。
线程造成的内存泄漏:
AsyncTask和Runnable都使用了匿名内部类,那么它们将持有其所在Activity的隐式引用。如果任务在Activity销毁之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏。
资源未关闭造成的内存泄漏:
1. BroadcastReceiver,ContentObserver 之类的没有解除注册
2. Cursor,Stream 之类的没有 close
3. 无限循环的动画在 Activity 退出前没有停止
4.一些其他的该 release 的没有 release,该 recycle 的没有 recycle… 等
使用ListView时造成的内存泄漏 :初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的View对象,同时ListView会将这些View对象缓存起来。当向上滚动ListView时,原先位于最上面的Item的View对象会被回收,然后被用来构造新出现在下面的Item。这个构造过程就是由getView()方法完成的,getView()的第二个形参convertView就是被缓存起来的Item的View对象(初始化时缓存中没有View对象则convertView是null)。
集合容器中的内存泄露:
我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
WebView造成的泄露:
Android 中的 WebView 存在很大的兼容性问题,有些 WebView 甚至存在内存泄露的问题。
解决办法:
为 WebView 开启另外一个进程,通过 AIDL 与主进程进行通信, WebView 所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。
那我们可以使用哪些工具来进行内存泄漏的检测???
Mermory Monitor:
位于Android Monitor中,该工具可以:
1. 方便的显示内存使用情况以及情况
2. 快速定位卡顿是否与GC有关
3. 快速定位潜在的内存使用问题(内存占用一直在增长)
4. (缺点)不能准确的定位问题
Allocation Tracker:
该工具用途:
1. 可以定位代码中分配的对象类型、大小、时间、线程、堆栈等信息
2. 可以定位内存抖动问题
3. 配合HeapViewer定位内存泄漏问题(可以找出泄漏的对象是在哪里创建等等)
使用方法:在Mermory Monitor点击Start Allocation Tracking按钮即可开始跟踪在点击停止跟踪后会显示统计结果。
Heap Viewer:
该工具用途:
1. 显示内存快照信息
2. 每次GC后收集一次信息
3. 查找内存泄漏的利器
使用方法:在Mermory Monitor点击Dump Java Heap 按钮,在统计报告中选package分类。配合Mermory Monitor的initiate GC按钮,可检测内存泄漏的情况。
Leak Canary:(被誉为内存泄漏检测神器,也是使用最广的工具)
一个在调试时就可以检测内存泄露的Java开源库它可以在我们的应用发生内存泄漏的时候发出提醒,提醒包括通知和Log。除了会在界面中显示泄漏信息之外,Log中也一样会输出泄漏的具体信息。除了能够发现并修复APP中的内存泄漏问题还可以发现一些Android SDK自身的内存泄漏问题,更加有效减少OOM错误。
LAST and the LAST,如何避免内存泄漏
1、在涉及使用Context时,对于生命周期比Activity长的对象应该使用Application的Context。凡是使用Context优先考虑Application的Context,当然它并不是万能的,对于有些地方则必须使用Activity的Context。对于Application,Service,Activity三者的Context的应用场景如下:
其中,NO1表示Application和Service可以启动一个Activity,不过需要创建一个新的task任务队列。而对于Dialog而言,只有在Activity中才能创建。除此之外三者都可以使用。
2、对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏。
3、对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null。
4、保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。
5、对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
1)将内部类改为静态内部类
2)静态内部类中使用弱引用来引用外部类的成员变量
参考链接:https://www.jianshu.com/p/bf159a9c391a
更多推荐
所有评论(0)