一,背景:

1.1,什么是内存泄漏

内存泄漏指程序在申请内存后,无法释放已申请的内存空间,导致系统无法及时回收内存并且分配给其他进程使用。

1.2,内存管理

1.3,垃圾回收

上面可以看出GC回收的主要对象是java堆,也就是new出来的对象。垃圾回收算法

标记-清除 算法

思想:

标记阶段:标记出所有需要回收的对象;

清除阶段:统一清除(回收) 所有被标记的对象

优点:

实现简单

缺点:

效率问题:标记和清除 两个过程效率不高

空间问题:标记-清除后,会产生大量不连续的内存碎片

场景:

对象存活率较低 & 垃圾回收行为频率低[如老年代)

复制算法

思想:

将内存分为大小相等的两块,每次使用其中一块,当使用的这块内存 用完,就将 这块内存上还存活的对象 复制到另一块还没试用过的内存上最终将使用的那块内存一次清理掉。

优点:

解决了标记-清除算法中 清除效率低的问题: 每次仅回收内存的一半区域

解决了标记-清除算法中 空间产生不连续内存碎片的问题:将已使用内存上的存活对象 移动到栈顶的指针,按顺序分配内存即可

缺点:

每次使用的内存缩小为原来的一半

当对象存活率较高的情况下需要做很多复制操作,即效率会变低

场景:

对象存活宰较低& 需要频繁进行垃圾回收 的区域(如新年代)》

标记 - 整理 算法

思想:

标记阶段:标记出所有需要回收的对象;

整理阶段:让所有存活的对象都向一移动

清除阶段:统一清除(回收) 端以外的对象

优点:

解决了标记-清除算法中 清除效率低的问题:一次清除端外区域

解决了标记-清除算法中 空间产生不连续内存碎片的问题:将已使用内存 步繁多:标记、整理、清除上的存活对象 移动到栈顶的指针,按顺序分配内存即可。

缺点:

步繁多:标记、整理、清除

场景:

对象存活率较低 & 垃圾回收行为频率低(如老年代)

分代收集 算法

思想:

根据对象存活周期的不同将Java堆内存分为:新生代&老年代

每块区域特点:

新生代:对象存活率较低& 垃圾回收行为频率高

老年代:对象存活率较低& 垃圾回收行为频率任

根据每块区域特点选择对应的垃圾收集算法《即上面介绍的算法)

新生代:采用复制算法

老年代:采用 标记-清除 算法、标记 - 整理 算法

优点:

效率高、空间利用率高:根据不同区域特点选择不同垃圾收集算法

缺点:

需要维护多个堆区域,增加了复杂性。可能会出现对象晋升的情况,导致老年代的内存压力增大。

场景:

虚拟机基本都采用这种算法

1.4,上面可以看出对象能回收就不会造成垃圾,也不会占用大量内存,所以在对象管理上一定要注意GC能及时回收没用对象,不然内存就会慢慢被占满,最终导致内存溢出,发生程序崩溃和ANR异常。

总结起来主要原因就是长生命周期对象引用短生命周期对象,造成短生命周期对象不能及时释放回收

造成内存泄漏的常见原因:

单例持有activity短生命周期

非静态内部类会持有外部类的引用

外部类中持有非静态内部类的静态对象

Handler 或 Runnable 作为非静态内部类

资源需要关闭,BroadcastReceiver、ContentObserver、File、Cursor、Bitmap集合对象没有及时清理引起的内存泄漏

二,分析内存泄漏的工具

2.1 leakcanary

leakcanary是一个监测android和java内存泄漏的工具。他能够在不影响程序正常运行的情况下,动态收集程序存在的内存泄漏问题

Github网站: https://github.com/square/leakcanary

使用,在app的build.gradle添加依赖

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:2.10'

在application初始化leakcanary

LeakCanary.Config config = LeakCanary.getConfig().newBuilder().retainedVisibleThreshold(3).computeRetainedHeapSize(false).build();
LeakCanary.setConfig(config);

2.2 AndroidStudio 的Profiler

 三,实例,记一次内存泄漏问题

3.1 我们平时用的手机可能性能比较强,而且不会一直运行使用,一般内存泄漏也不会引起app瘫痪。但遇到开发板这种泄漏问题就会成为致命错误,由于内存小,运行时间长,万一发生内存泄漏就会一直增加内存,直至内存耗尽引发程序崩溃

3.2 近期引发上述问题的地方是一个扫码付款设备,功能是打开摄像头识别二维码,异步线程扫码二维码后退出扫码页面,最后定位到的内存泄漏地方是ActivityUtils页面管理工具类造成的内存泄漏

3.3 引起内存泄漏的源码:

mCamera.setDisplayOrientation(270);//竖屏显示
mCamera.setPreviewDisplay(mHolder);
mCamera.setPreviewCallback(previewCallback);
mCamera.startPreview();
mCamera.autoFocus(autoFocusCallback);
PreviewCallback previewCallback = new PreviewCallback() {public void onPreviewFrame(byte[] data, Camera camera) {if (data != null) {Camera.Parameters parameters = camera.getParameters();Size size = parameters.getPreviewSize();//获取预览分辨率//创建解码图像,并转换为原始灰度数据,注意图片是被旋转了90度的Image source = new Image(size.width, size.height, "Y800");//图片旋转了90度,将扫描框的TOP作为left裁剪source.setData(data);//填充数据ArrayList<HashMap<String, String>> result = new ArrayList<>();//解码,返回值为0代表失败,>0表示成功int dataResult = mImageScanner.scanImage(source);if (dataResult != 0) {playBeepSoundAndVibrate();//解码成功播放提示音SymbolSet syms = mImageScanner.getResults();//获取解码结果for (Symbol sym : syms) {HashMap<String, String> temp = new HashMap<>();temp.put(ScanConfig.TYPE, sym.getSymbolName());temp.put(ScanConfig.VALUE, sym.getResult());result.add(temp);}if (result.size() > 0) {finish();EventBusUtil.sendEvent(new Event(EventCode.EVENT_QRCODE, result.get(0).get(ScanConfig.VALUE)));} }AsyncDecode asyncDecode = new AsyncDecode();asyncDecode.execute(source);//调用异步执行解码}}
};  
private class AsyncDecode extends AsyncTask<Image, Void, ArrayList<HashMap<String, String>>> {@Overrideprotected ArrayList<HashMap<String, String>> doInBackground(Image... params) {final ArrayList<HashMap<String, String>> result = new ArrayList<>();Image src_data = params[0];//获取灰度数据//解码,返回值为0代表失败,>0表示成功final int data = mImageScanner.scanImage(src_data);if (data != 0) {playBeepSoundAndVibrate();//解码成功播放提示音SymbolSet syms = mImageScanner.getResults();//获取解码结果for (Symbol sym : syms) {HashMap<String, String> temp = new HashMap<>();temp.put(ScanConfig.TYPE, sym.getSymbolName());temp.put(ScanConfig.VALUE, sym.getResult());result.add(temp);if (!ScanConfig.IDENTIFY_MORE_CODE) {break;}}}return result;}@Overrideprotected void onPostExecute(final ArrayList<HashMap<String, String>> result) {super.onPostExecute(result);if (!result.isEmpty()) {EventBusUtil.sendEvent(new Event(EventCode.EVENT_QRCODE, result.get(0).get(ScanConfig.VALUE)));finish();} else {isRUN.set(false);}}}
ActivityUtil.finishExcept(MainActivity.class);

3.4 造成的结果:

Profiler分析结果,GC回收频繁,内存抖动严重

内存在5分钟内从128以下上到快200多,增加了近一倍

分析结果,可以看到300多次内存泄漏

三个地方引发的内存泄漏

leakcanary 检测结果 ActivityUtils引发的泄漏

程序崩溃

3.5 原因分析

异步任务持有activity的引用,activity关闭后但任务还没结束,导致ActivityUtils工具类不能finish结束页面,造成页面不能回收,从而引发内存泄露。

3.6 优化解决

第一步 摄像头扫描帧预览优化,改用有缓冲区的

mCamera.addCallbackBuffer(new byte[parameters.getPreviewSize().width * parameters.getPreviewSize().height * 3 / 2]);
mCamera.setPreviewCallbackWithBuffer(previewCallback);//不管有没有数据都在预览最后加进缓冲区
PreviewCallback previewCallback = new PreviewCallback() {public void onPreviewFrame(byte[] data, Camera camera) {camera.addCallbackBuffer(data);}
}
mCamera.setDisplayOrientation(270);//竖屏显示
mCamera.setPreviewDisplay(mHolder);
//mCamera.setPreviewCallback(previewCallback);
mCamera.addCallbackBuffer(new byte[parameters.getPreviewSize().width * parameters.getPreviewSize().height * 3 / 2]);
mCamera.setPreviewCallbackWithBuffer(previewCallback);
mCamera.startPreview();
mCamera.autoFocus(autoFocusCallback);
PreviewCallback previewCallback = new PreviewCallback() {public void onPreviewFrame(byte[] data, Camera camera) {    if (data != null) {if (isRUN.compareAndSet(false, true)) {Camera.Parameters parameters = camera.getParameters();Size size = parameters.getPreviewSize();//获取预览分辨率//创建解码图像,并转换为原始灰度数据,注意图片是被旋转了90度的Image source = new Image(size.width, size.height, "Y800");//图片旋转了90度,将扫描框的TOP作为left裁剪source.setData(data);//填充数据ArrayList<HashMap<String, String>> result = new ArrayList<>();//解码,返回值为0代表失败,>0表示成功int dataResult = mImageScanner.scanImage(source);if (dataResult != 0) {playBeepSoundAndVibrate();//解码成功播放提示音SymbolSet syms = mImageScanner.getResults();//获取解码结果for (Symbol sym : syms) {HashMap<String, String> temp = new HashMap<>();temp.put(ScanConfig.TYPE, sym.getSymbolName());temp.put(ScanConfig.VALUE, sym.getResult());result.add(temp);if (!ScanConfig.IDENTIFY_MORE_CODE) {break;}}syms.destroy();syms = null;if (result.size() > 0) {isRUN.set(true);finish();EventBusUtil.sendEvent(new Event(EventCode.EVENT_QRCODE, result.get(0).get(ScanConfig.VALUE)));} else {isRUN.set(false);}} else {isRUN.set(false);}}}camera.addCallbackBuffer(data);
}

第二步,去掉异步任务和ActivityUtils相关的源码

优化后结果:

可以看到30分钟内存始终维持在128M以下,而且非常平稳

最后分析结果也是0个泄漏

后面经过连续4个小时测试,内存始终还是在128M以下,完美解决

3.7 总结:

  • 发生内存泄漏,先要找到泄漏的地方,leakcanary和Profiler同时分析,快速定位地方。
  • 泄漏的主要特征就是内存不断飙升,容易引发的就是长生命周期持有短生命周期对象,导致短生命周期无法释放。
  • 所以在开发中要注意这点,短生命周期结束时候一定要先结束长生命周期的任务,让长生命周期的对象释放掉短生命周期的引用,才能使短生命周期的对象顺利结束和回收
  • 对于长生命周期尽量使用application的上下文,避免短生命周期的对象无法释放。

Android之 内存泄漏问题检测和解决相关推荐

  1. Android内存泄漏的检测流程、捕捉以及分析

    https://blog.csdn.net/qq_20280683/article/details/77964208 Android内存泄漏的检测流程.捕捉以及分析 简述: 一个APP的性能,重度关乎 ...

  2. Android之内存泄漏以及解决办法(持更)

    Android之内存泄漏以及解决办法 文章链接:http://blog.csdn.net/qq_16628781/article/details/67761590 知识点: 单例造成的内存泄漏原因和解 ...

  3. 内存泄漏的检测、定位和解决经验总结

    内存泄漏的检测.定位和解决经验总结 温辉敏(wenhm@sina.com) 2006 年 05 月 [摘要] 结合局端MCU项目中CSS.NMS模块内存泄漏检测.修正的过程,简要介绍了内存泄漏检测的工 ...

  4. android inputmethodmanager内存泄露,Android InputMethodManager内存泄漏

    最近通过LeakCanary检测内存泄漏,发现一个很莫名其妙的问题,截图如下所示: 内存泄漏 从这里可以看到,InputMethodManager的mNextServedView持有了一个Recycl ...

  5. Android常见内存泄漏

    1.什么是内存泄露 内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃(内存溢出OOM)等严重后果. 内存 ...

  6. VS C/C++控制台程序添加内存泄漏自动检测功能

    基于MFC框架的应用程序由模板生成时,已经自动添加了内存泄漏自动检测功能,当你的程序有内存泄漏,在Debug调式模式下运行就会在vc的输出窗口里显示出来,容易发现并及时解决.但是我们在写一些测试程序时 ...

  7. android分析内存工具,Android Studio内存泄漏分析工具汇总

    Android Studio内存泄漏分析工具汇总 时间:2017-04-25     来源:Android开发学习网 在Android开发过程中,让人头疼的就是内存泄露问题了,很小的一个错误都会引起内 ...

  8. Android 中内存泄漏的原因和解决方案

    之前研究过一段时间关于 Android 内存泄漏的知识,大致了解了导致内存泄漏的一些原因,但是没有深入去探究,很多细节也理解的不够透彻,基本上处于一种似懂非懂的状态,最近又研究了一波,发现有很多新的收 ...

  9. Android Native内存泄漏诊断

    Android Native内存泄漏诊断 1.基础诊断方法 特点:操作简单,但只能判断是否有泄漏,但需使用者自行判断泄漏在哪里 命令行方式 adb shell dumpsys meminfo vStu ...

最新文章

  1. 专利分析与申请(1):法国汤姆森公司关于在有损编码器上扩展无损编码器的专利分析...
  2. Tungsten Fabric SDN — 操作实践 — Virtual Networks L2/L3 互联
  3. mysql concat 去掉重复_mysql - concat字段具有共同的重复密钥,并删除那些重复项,从而留下一个 - 堆栈内存溢出...
  4. 讲师征集| .NET Conf China 2021正式启动!
  5. OpenCV中绘制外围矩形框和圆框
  6. Python简单GUI(录音机)
  7. 马哥linux2018目录,2018-01-02 马哥Linux学习笔记—Linux系统基础使用入门
  8. ae渲染出现错误是什么问题_AE渲染输出损坏怎么解决?教你如何渲染才是正确的...
  9. matlab if函数嵌套函数,Excelif函数嵌套多层使用VLOOKUP函数实现多级条件嵌套搜索方法...
  10. Fibonacci 斐波那契数列的R语言实现
  11. 基于JAVA民航售票管理系统计算机毕业设计源码+数据库+lw文档+系统+部署
  12. linux /etc/motd,Linux 修改进站提示 /etc/motd
  13. 华为服务器2288h v5安装系统,华为2288装系统
  14. Python实现文本替换
  15. 菜鸟的MySQL学习之旅(二)—查询语句
  16. Quartus II 13.0sp1 (64-bit)使用教程
  17. 老男孩教育 | 27岁从月薪4K到月薪14K,5个月的时间完美蜕变!
  18. 来自“啄木鸟社区”的奋起宣言
  19. Java中的封装和继承思想,模拟考试阅卷系统
  20. 万能码创造更多希望(安全扫码专业委员会)

热门文章

  1. 使用pip安装aiohttp出现问题,和yarl和multidict有关
  2. 程序员的浪漫——用Python画一颗会发光的圣诞树
  3. 小程序用php介入直播功能,微信小程序直播接入指南
  4. 机器学习基础思维导图
  5. 山东大学软件学院创新实训——飞讯(二)
  6. 用IP段区分境内外用户
  7. sift行人检测matlab,干货!一文读懂行人检测算法
  8. 云计算学习基础,云计算学习课程
  9. Ubuntu和Windows双系统蓝牙键盘配对
  10. UltraEdit-32 13.20 注册序列号