1、背景
在开发App的过程中我们会经常遇到内存抖动或者内存泄露的问题,我们需要用一些常用的工具去定位这些问题,但是像leakCarray之类的工具无脑的使用,总有一些地方是分析不了的,所以我们还是需要去分析内存泄露的本质,我们需要去了解所谓泄露的真正原因。
2、内存抖动
2.1、定义
短时间内有大量对象创建销毁,它伴随着频繁的GC,所以就会有内存一边创建一边被GC掉的情况,只是在面试的时候容易搞出高大上的名词,把开发者问住了,我们可以通过Profile就可以检测出来了
2.2、举个例子
我循环遍历出于很多没有使用的对象,这个对象可能没有使用到,会被GC回收掉,但是我又再不断地创建
findViewById<Button>(R.id.leak_more).setOnClickListener {
for (index in 0..10000) {
var paint = Paint()
}
}
可以看到图是一个动态的齿状,一边创建出来 一边又被回收了,所以产生了这样的波浪状
我们打开AndroidStudio在有问题的地方划上一个区域,下边会弹出一个对应的对象,你评估哪些对象个数是不合理的,像上面的例子就是Paint 是不合理的,一段时间内出现了十万个,点击字符串会出现右上角的对象,再点击右上角会出现代码在哪一行创建的,可以定位创建的地点,就可以定位解决问题了。
2.3预防
(1)避免在循环中创建对象;
(2)避免在频繁调用的方法中创建对象,如View的onDraw方法;
(3)允许复用的情况下,使用对象池进行缓存,如:Handler的Message单链表(obtain)
3、内存泄露
程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,一般来说长生命周期对象持有短生命周期对象强引用,从而导致短生命周期对象无法被回收!
3.1、原理 可达性分析法
通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所有的引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的
3.2、软引用,弱引用
软引用:定义一些还有用但并非必须的对象。对于软引用关联的对象,GC不会直接回收,而是在系统将要内存溢出之前才会触发GC将这些对象进行回收。
弱引用:同样定义非必须对象。被弱引用关联的对象在GC执行时会被直接回收。
3.3、检测
内存泄露检测的方式有比较多种,之前其他地方有介绍过LeakCarray的使用和原理,今天主要介绍使用MAT工具进行检测
故意写了一个handler 内存泄露
从memory profile 取出一段内存,这段内存可能就存在内存泄露的信息,我们进行分析一下
我取一些样本比如我们保存为 15.hprof 然后我们需要用Android SDK自带的 hprof-conv 环境怎么配我就不在这里细说了,需要的小伙伴自信查阅一下资料
然后我们使用命令
hprof-conv -z 15.hprof 16.hprof 转换一下 然后用 elipse的mat 打开 一下 mat 没有的去下载就可以了,下载和环境配置由于篇幅问题就不在这里细讲了
用mat工具打开转换后的hprof文件,我们可以看到首页长这样
找到Histogram将它打开,我们可以看到好多个对象,先找到你要判别的对象,然后将他们的各种引用给去掉
如果是这样的图说明去掉各种应用后这个对象还是存在的,也就是GC不掉的,它存在引用,所以GC是回收不了的,如果这个不在你的预计之中,比如这个Activity在你的计划之中,是应该finish掉的,但是它现在却存在且无法GC回收,那就是要考虑是不是存在内存泄露
如果结果是这样的,那么就说明不存在这个内存泄露的可能,对象都没有引用
以上就是通过MAT工具进行查看内存的回收情况,其本质还是紧紧围绕长生命周期的对象有没有引用到短生命周期的对象,判断是否泄露的标准还是可达性分析。
4、使用AndroidStudio Profiler进行内存泄露分析
4.1、使用AndroidStudio现在就支持内存泄露的检查
举个例子
有一个前置Activity
class LeakBeforeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_memory_leak_before)
findViewById<Button>(R.id.start_next).setOnClickListener {
var intent = Intent(this, MemoryLeakActivity::class.java)
startActivity(intent)
}
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:ignore="MissingDefaultResource">
<Button
android:id="@+id/start_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="跳转到下一页"></Button>
</LinearLayout>
第二个Activity
class MemoryLeakActivity : AppCompatActivity(),CallBack {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_memory_leak);
val imageView = findViewById<ImageView>(R.id.iv_memoryleak)
val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.icon)
imageView.setImageBitmap(bitmap)
CallBackManager.addCallBack(this)
findViewById<Button>(R.id.finish).setOnClickListener {
finish()
}
}
}
class CallBackManager {
companion object {
val sCallBacks = ArrayList<CallBack>()
fun addCallBack(call: CallBack) {
sCallBacks.add(call)
}
fun removeCallBack(call: CallBack) {
sCallBacks.remove(call)
}
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:ignore="MissingDefaultResource">
<ImageView
android:id="@+id/iv_memoryleak"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="哈哈哈"></ImageView>
<Button
android:id="@+id/finish"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="结束"
></Button>
</LinearLayout>
我们反复开关Activty Activity就会被静态列表所引用,导致无法被回收,导致内存泄露
val sCallBacks = ArrayList
划定一块内存dump 一块内存看
我们从dump的数据可以看到 分配的内存情况,哪些数据占用了大内存,包括内存的引用情况都可以看到,值得注意的是左上角提示了我们有四个内存泄露,我们需要重点关注,我们点击进去看看,内存具体泄露在了哪里
我们可以看到内存泄露的Activity 点击后点击右下角的应用,勾上显示引用的路径,我们就可以看到引用的路劲这样就知道为什么内存泄露 从而进行解决了。
个人是比较喜欢这种方式解决内存泄露的
5、常见的内存泄露的场景如下,开发的时候记得多进行检讨
1、Handler持有的引用最好使用弱引用,在Activity被释放的时候要记得清空Message,取消Handler对象的Runnable;
2、非静态内部类、非静态匿名内部类会自动持有外部类的引用,为避免内存泄露,可以考虑把内部类声明为静态的;
3、对于生命周期比Activity长的对象,要避免直接引用Activity的context,可以考虑使用ApplicationContext;
4、广播接收器、EventBus等的使用过程中,注册/反注册应该成对使用;
5、不再使用的资源对象Cursor、File、Bitmap等要记住正确关闭;
6、集合里面的东西、有加入就应该对应有相应的删除;