凡所有相皆是虚妄若见诸相非相即见如来。 ------------佛说
内存抖动
内存抖动通常是指在短时间内发生了多次内存的分配与释放,主要原因则是短时间内频繁的创建对象,为了应对这种情况,虚拟机会频繁的触发GC操作,当GC进行时,其他线程会被挂起等待GC完成,频繁GC,也就导致比如UI在绘制时超过16ms一帧,导致画面卡顿等,接下来用代码实操内存抖动。
代码很简单就是每隔一段时间new对象,接下来当我打开APP点击button以后在AndroidStudio的Profiler内存检测窗口中可以看到抖动前与抖动后的图。
图一无内存抖动的示意图,图二内存抖动,在内存观察窗口像出现这种带有锯齿装的曲线图的时候就说明发生了内存抖动
解决方案
1 不要再for循环里面频繁的new对象
2 不要再自定义View的时候再ondraw里面频繁的创建对象
3 如果非要构建一些对象我们破不得已最好使用对象池。
内存泄漏
内存泄漏: 一个不在被程序使用的对象或者变量依旧存活再内存中无法被回收;(强引用)
内存溢出:当程序申请内存时,没有足够的内存供程序使用
比较小的内存泄露并不会有太大的影响,但内存泄露的多了,占用的内存空间就更大,程序正常需要申请使用的内存则会相应减少。
接下来我们用一段实例代码来模拟内存泄漏以及通过工具定位内存泄漏最后如何解决。
上面这段代码存在内存泄漏,因为mHandler的创建要依赖于MainActivity对象,onCreate里面有这么一段mHandler.sendEmptyMessageDelayed(HANDLER_FLAG,1000); handleMessage里面有这么一段,这两句一执行其实就相当于一个死循环,导致mHandler一直存活,mHandler呢又引用当前Activity的对象,这样一来Activity虽然关闭了,onDestroy虽然执行了,然是Activity并没有被GC回收,导致内存泄漏。补充mHandler为啥会持有Activity对象,因为mHandler是通过匿名内部类构建出来的,new Handler(){}; 匿名内部类的实例化必须要依赖于外部类的对象而实例化,所以匿名内部类持有外部类对象,也就是当前MainActivity.this。
使用profiler窗口分析工具分析内存泄漏
打开AndroidStudio----->点击Profiler----->打开MEMORY-----运行上面代码-----打开APP----然后关闭------点击Profiler左上角的垃圾桶图标---截取垃圾桶后面一小段-----Live Alloction这里先择 app heap然后选择Arrange by package按包名分析,找到自己应用的包名 如下图所示
内存泄漏窗口我们只关注Total Count这一列,这一列表示的是当前我们这个对象在内存中有多少个,Shallow Size这一列代表的是当前这个对象在内存中占用了多大,,由图可以看出,我们的MainActivity已经finish掉了,但是呢在内存窗口里面显示的还是1,那么说明我们当前的这个MainActivity并没有销毁,存在内存泄漏。现在通过AS的profiler分析工具已经分析出来了是否内存泄漏,内存泄漏确实存在,那么我们怎么定位到底是哪里存在内存泄漏?这个时候MAT 分析工具要登场了,下载地址https://www.eclipse.org/mat/downloads.php。我们先在刚才暂停的内存窗口点击继续运行让内存分析继续执行,然后点击下载,入下图
当点击下载以后工具会帮我们自动截取一段内存快照,然后我们右键快照部分选择Export,将文件到处,保存路径根据自己的实际需求来。导出过程入下图,会得到一个.hprof结尾的文件。
文件格式转换
因为我们直接到处的.hprof结尾的文件不能直接让MAT进行使用,所以还需要一下文件格式的转换,转换步骤如下。
第一步 进入到自己安卓SDK目录文件夹下面,打开platform-tools文件,找到hprof-conv.exe
第二步,打开控制台进入cd到刚才导出的.hprof文件的文件夹,我的是在C盘test目录,所以直接就cd到C:\test。
第三步 执行转换命令 hprof-conv -z memory-20200822T165352.hprof memory-20200822T165352.mat.hprof
第四不 打开刚才前面网站下载下来的mat工具,点击File--->open heap dump将刚才的转换的文件导入,导入以后会如下如所示
分析流程
点击mat扇形区域下方的Histogram进入到分析页面,进入到分析页面以后,在Class Name这一列的最上方搜索当前关闭并没有销毁的Activity也就是MainActivity.到Histogram页面以后按照如下图的步骤操作
由mat分析工具可以查看引用链条,最终的结论就是mMainLooper引用了mQueue,mQueue引用了mMessage,mMessage引用了target(也就是我们的mHandler),mHandler引用了this,这个this也就是MainActivity.也就是说我们当前这个MainActivity的内存泄漏是由mHandler所引起的。
解决方案
1 new Handler的时候将Handler声明成静态的,为啥要申明成静态的??????因为静态内部类实例化的时候不需要依赖于外部类对象,因为在类加载的时候我们的Java虚拟机优先会将static的加载进去。
范例:
private static Handler mHandler=new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
mHandler.sendEmptyMessageDelayed(HANDLER_FLAG,1000);
}
};
优点:不会内存泄漏
缺点:如果频繁使用static会导致jvm加载类的时候消耗过多,为什么???因为静态的东西jvm会优先加载,比如平时jvm加载的时候只需要1s,如果代码中static用的过多,jvm加载的时候会花费更长的时间
2 使用WeakReference
优点:GC回收时,就会自动清除
缺点:不知道什么时候被回收
3 调用mHandler.removeCallbacksAndMessages(null);只有调用了mHandler.removeCallbackAndMessages的时候才会让Looper内部的Handler置空。写Handler的时候最好用callback写,而不是直接在Handler里面实现handleMessage方法 示例
private static Handler mHandler=new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
return false;
}
});