内存分析工具使用
优化基础
前言
内存优化主要是为了解决OOM(out of memory),一点点的溢出并不会出现大问题,app功能集成的越多,功能越复杂,不可避免的会出一些内存泄露的问题。
优化的原则:
- 对象在不需要的时候,需要销毁,置null
- 对象不能被及时销毁,则对象需要被复用
内存基础知识
- 栈:保存局部变量的值,包括:1.用来保存基本数据类型的值;2.保存类的实例,即堆区对象的引用(指针)。
- 堆:用来存放动态产生的数据,比如new出来的对象。注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。
常量池:JVM为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用(1)。池中的数据和数组一样通过索引访问。由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在Java的动态链接中起了核心作用。常量池存在于堆中。
Java内存分配中的栈:在函数中定义的一些基本类型的变量数据和对象的引用变量都在函数的栈内存中分配。
当在一段代码块定义一个变量时,Java就在栈中 为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。- Java内存分配中的堆:堆内存用来存放由new创建的对象和数组。 在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。
在堆中产生了一个数组或对象后,还可以 在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。 引用变量就相当于是 为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。引用变量就相当于是为数组或者对象起的一个名称。
引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序 运行到使用 new 产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍 然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。这也是 Java 比较占内存的原因。
上图简单分析了下内存模型,在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的。
- OOM原因:当栈中只要存在一个引用,这个引用指向堆中的一个对象。这个对象永远不会被GC回收。当内存中的对象越来越多,系统给进程分配的内存被使用完毕,则会发生OOM.
GC系统
栈内存是由系统自动控制的,局部变量用完,自动回收了。而堆内存是由GC系统控制的,我们能做的就是告诉GC回收我们不用的对象。
GC系统是更具GC Root算法进行垃圾回收工作的,GC系统的算法会以一个GCroot为节点,搜索与之关联的对象,如果某个对象与GC root对象没有引用链,则表示该对象可被回收。android中许多情况都可能导致,一些对象绕过GC root回收算法,从而不能及时回收对象,而造成泄露。
常见的GC root 对象:
- class :由System class loader 加载的对象
- JNI : jni相关调用的引用,变量,参数。
- Thread : 活着的线程
- Stack : 栈中的对象
- 静态:方法区类的静态属性引用的对象
- 常量: 方法区中的常量引用的对象(final 类型)
内存泄露检测工具
知道了原因,就该想办法解决问题,工欲善其事必先利其器,先介绍下一些工具:(工具配合使用,效果更佳)
- Android studio Memory Monitor
android stuido 自带的内存工具,可以查看内存实时情况。还可以Dump java Heap 进行分析。类似与DDMS中的分析工具,使用更加方便。
- Dalvik Debug Monitor Service(DDMS)
内部有一些集成的检测内存工具,比Memory Monitor复杂一些。
- Memory Analysis Tool (MAT)
老牌分析工具可以详细分析内存分配情况,通常用它分析内存泄露情况。
- LeakCannry
经过上面一轮工具的使用。头会大的。而LeakCannry简单方便。
俗话说:科技是第一生产力。
如果MAT是单反,LeakCannry则是卡片机。傻瓜式使用,内存泄露情况会非常简单的会显示出来。省去了分析的步骤。当然泄露还是得自己解决。
下面是LeakCannry的简单使用:
LeakCannry
引入aar包并初始化
目前1.5.2有个bug.如果项目分包了,会报错。只能使用低版本的。
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
然后在Application里初始化
//内存泄漏检测
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
运行到手机上进行检测
安装完成后会发现手机上有两个新的app,一个是项目本身,一个是检测的app。现在打开自己的app,开始进行monkey操作,也可以测试,有可能泄露的地方。如果出现泄露,app会出现一个弹窗,并开始自动dump分析内存信息。在检测的app中可以查看详情。
发几张图
一个是单例引发的泄露,一个是内部handler引发的泄露。下面介绍几种常见的内存泄露。及建议。
常见内存泄露分析
单例(static)
这个是最常见的泄露,开发表情键盘的时候,一个单例工具类持有context类引用,这个context是activity层次的。所以当这个activity销毁了。单例里是静态引用的。所以这个工具类持有activity的强引用。因此activity得不到释放,而发生泄露。
内部类
handler内部类持有外部activity引用。这类也比较常见,handler比较特殊。一般在网络请求中将请求结果发送一个handler,通知界面更新。当网络不好的情况下,用户会直接退出activity。这时候后台线程还持有这个handler.这个handler持有activity.所以会引发泄露。可以在activity退出的时候结束线程。
特别注意的是非静态内部类会持有外部类。
其他
以前总结过一点,只是似懂非懂的感觉,现在回过头看以前的总结。发现一般都是上面两种情况的变种。资源类不要忘记关闭,循环动画要结束,广播要解绑。
获取更多内存
fork子进程
使用JNI
OpenGL
LargeHeap
最简单的方式,在清单文件application中直接申请。但是大多数国产机已经把系统内部实现改成默认大小了,无论加不加都是那么大。测试的机器只有三星Note4可以使用,默认256M,申请过后512M
android:largeHeap="true"