Android Studio +MAT 分析内存泄漏实战

简介:

对于内存泄漏,在Android中如果不注意的话,还是很容易出现的,尤其是在Activity中,比较容易出现,下面我就说下自己是如何查找内存泄露的。

首先什么是内存泄漏?

内存泄漏就是一些已经不使用的对象还存在于内存之中且垃圾回收机制无法回收它们,导致它们常驻内存,会使内存消耗越来越大,最终导致程序性能变差。 
其中在Android虚拟机中采用的是根节点搜索算法枚举根节点判断是否是垃圾,虚拟机会从GC Roots开始遍历,如果一个节点找不到一条到达GC Roots的路线,也就是没和GC Roots 相连,那么就证明该引用无效,可以被回收,内存泄漏就是存在一些不好的调用导致一些无用对象和GC Roots相连,无法被回收。

既然知道了什么是内存泄漏,自然就知道如何去避免了,就是我们在写代码的时候尽量注意产生对无用对象长时间的引用,说起来简单,但是需要足够的经验才能达到,所以内存泄漏还是比较容易出现的,既然不容易完全避免,那么我们就要能发现程序中出现的内存泄漏并修复它, 
下面我就说说如何发现内存泄漏的吧。

查找内存泄漏:

比如说下面这个代码:

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String string = new String(); } public void click(View view){ Intent intent = new Intent(); intent.setClass(getApplicationContext(),SecondActivity.class); startActivity(intent); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
public class SecondActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); Runnable runnable = new Runnable() { @Override public void run() { try { Thread.sleep(8000000L); } catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(runnable).start(); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

每次跳转到这个Activity中时都会调用一个线程,然后这个线程会执行runnable的run方法 由于Runnable是一个匿名内部对象 所以握有SecondActivity的引用,因此 
很简单的两个Activity,可由MainActivity跳转到SecondActivity中, 
下面我们从MainActivity跳到SecondActivity 然后从SecondActivity返回MainActivity,连续这样5次 ,最终返回MainActivity,按照常理来说,我们从SecondActivity返回MainActivity之后 SecondActivity就该被销毁回收,可实际可能并不是这样。

这时候要判断发没发生内存溢出就要使用工具了!下面有两种方式

1.利用MAT工具查找

首先打开AS中的Android Device Monitor工具 具体位置如下图: 
AS Android Device Monitor位置
打开后会出现如下的界面 
ADM界面
先选中你要检测的应用的包名,然后点击下图画圈的地方,会在程序包名后标记一个图标 

接下来要做的就是操作我们的app 来回跳转5次。 
之后点击下图的图标 就可导出hprof文件进行分析了 

导出文件如下图所示: 
hprof文件 
得到了hprof文件 我们就可以利用MAT工具进行分析了, 
打开MAT工具 
如果没有 可以在下面网址下载 
MAT工具下载地址 

界面如下图所示: 

打开我们先前导出的hprof文件 ,不出意外会报下面的错误 

这是因为MAT是用来分析Java程序的hprof文件的 与Android导出的hprof有一定的格式区别,因此我们需要把导出的hprof文件转换一下,sdk中提供给我们转换的工具 hprof-conv.exe 在下图的位置
hprof-conv位置 
接下来我们cd到这个路径下执行这个命令转换我们的hprof文件即可,如下图 
转换hprof文件 
其中 hprof-conv 命令 这样使用 
hprof-conv 源文件 输出文件 
比如 hprof-conv E:\aaa.hprof E:\output.hprof 
就是 把aaa.hprof 转换为output.hprof输出 output.hprof就是我们转换之后的文件,图中 mat2.hprof就是我们转换完的文件。

接下来 我们用MAT工具打开转换之后的mat2.hprof文件即可 ,打开后不报错 如下图所示: 
MAT打开hprof文件
之后我们就可以查看当前内存中存在的对象了,由于我们内存泄漏一般发生在Activity中,因此只需要查找Activity即可。 
点击下图中标记的QQL图标 输入 select * from instanceof android.app.Activity 
类似于 SQL语句 查找 Activity相关的信息 点击 红色叹号执行后 如下图所示: 
QQL

接下来 我们就可以看到下面过滤到的Activity信息了 
如上图所示, 其中内存中还存在 6个SecondActivity实例,但是我们是想要全部退出的,这表明出现了内存泄漏

其中 有 Shallow size 和 Retained Size两个属性

Shallow Size
对象自身占用的内存大小,不包括它引用的对象。针对非数组类型的对象,它的大小就是对象与它所有的成员变量大小的总和。
当然这里面还会包括一些java语言特性的数据存储单元。针对数组类型的对象,它的大小是数组元素对象的大小总和。
Retained Size
Retained Size=当前对象大小+当前对象可直接或间接引用到的对象的大小总和。(间接引用的含义:A->B->C, C就是间接引用)
不过,释放的时候还要排除被GC Roots直接或间接引用的对象。他们暂时不会被被当做Garbage。

接下来 右击一个SecondActivity 

选择 with all references 
打开如下图所示的页面 

查看下图的页面 
看到 this0Activitythis0是表示 内部类的意思,也就是一个内部类引用了Activity 而 this$0又被 target引用 target是一个线程,原因找到了,内存泄漏的原因 就是 Activity被 内部类引用 而内部类又被线程使用 因此无法释放,我们转到这个类的代码处查看

public class SecondActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); Runnable runnable = new Runnable() { @Override public void run() { try { Thread.sleep(8000000L); } catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(runnable).start(); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

确实 在 SecondActivity中 存在Runnable 内部类对象,然后又被线程 使用,而线程要执行8000秒 因此 SecondActivity对象被引用 无法释放,导致了内存溢出。 
要解决这种的内存溢出,要及时在Activity退出时结束线程(不过不大好结束。。),或者良好的控制线程执行的时间即可。

这样我们就找出了这个程序中的内存溢出。

2.直接利用Android Studio的 Monitor Memory 查找内存溢出 
还是利用上面那个程序,我就简单点说了。

首先 在手机上运行程序,打开AS的 Minotor 界面 查看Memory 图像 

点击 小卡车图标(图中1位置图标) 可以触发一次 GC 

点击 图中2位置图标可以查看hprof文件 

左边是 内存中的对象,在里面找 Activity 看存不存在我们希望已经回收的Activity 如果 出现我们期望已经回收的Activity,单击 就会在右边显示它的总的个数,点击右边的某个,可以显示 它的GC Roots的树关系图 ,查看关系图就可以找出发生内存泄漏的位置(类似于第一种方式)

这样就完成了内存泄漏的查找。

其中内存泄漏产生的原因在Android中大致分为以下几种:

1.static变量引起的内存泄漏 
因为static变量的生命周期是在类加载时开始 类卸载时结束,也就是说static变量是在程序进程死亡时才释放,如果在static变量中 引用了Activity 那么 这个Activity由于被引用,便会随static变量的生命周期一样,一直无法被释放,造成内存泄漏。

解决办法: 
在Activity被静态变量引用时,使用 getApplicationContext 因为Application生命周期从程序开始到结束,和static变量的一样。

2.线程造成的内存泄漏 
类似于上述例子中的情况,线程执行时间很长,及时Activity跳出还会执行,因为线程或者Runnable是Acticvity内部类,因此握有Activity的实例(因为创建内部类必须依靠外部类),因此造成Activity无法释放。 
AsyncTask 有线程池,问题更严重

解决办法: 
1.合理安排线程执行的时间,控制线程在Activity结束前结束。 
2.将内部类改为静态内部类,并使用弱引用WeakReference来保存Activity实例 因为弱引用 只要GC发现了 就会回收它 ,因此可尽快回收

3.BitMap占用过多内存 
bitmap的解析需要占用内存,但是内存只提供8M的空间给BitMap,如果图片过多,并且没有及时 recycle bitmap 那么就会造成内存溢出。

解决办法: 
及时recycle 压缩图片之后加载图片

4.资源未被及时关闭造成的内存泄漏 
比如一些Cursor 没有及时close 会保存有Activity的引用,导致内存泄漏

解决办法: 
在onDestory方法中及时 close即可

5.Handler的使用造成的内存泄漏 
由于在Handler的使用中,handler会发送message对象到 MessageQueue中 然后 Looper会轮询MessageQueue 然后取出Message执行,但是如果一个Message长时间没被取出执行,那么由于 Message中有 Handler的引用,而 Handler 一般来说也是内部类对象,Message引用 Handler ,Handler引用 Activity 这样 使得 Activity无法回收。

解决办法: 
依旧使用 静态内部类+弱引用的方式 可解决

其中还有一些关于 集合对象没移除,注册的对象没反注册,代码压力的问题也可能产生内存泄漏,但是使用上述的几种解决办法一般都是可以解决的。

参考资料: 
使用Android studio分析内存泄露

Android内存泄漏分析及调试

java对象的强引用,软引用,弱引用和虚引用

Android内存泄漏终极解决篇(下)






    本文转自 一点点征服   博客园博客,原文链接:http://www.cnblogs.com/ldq2016/p/6632338.html,如需转载请自行联系原作者

相关文章
|
12天前
|
安全 Android开发 Kotlin
Android经典实战之SurfaceView原理和实践
本文介绍了 `SurfaceView` 这一强大的 UI 组件,尤其适合高性能绘制任务,如视频播放和游戏。文章详细讲解了 `SurfaceView` 的原理、与 `Surface` 类的关系及其实现示例,并强调了使用时需注意的线程安全、生命周期管理和性能优化等问题。
44 7
|
5天前
|
Java 测试技术 Android开发
Android性能测试——发现和定位内存泄露和卡顿
本文详细介绍了Android应用性能测试中的内存泄漏与卡顿问题及其解决方案。首先,文章描述了使用MAT工具定位内存泄漏的具体步骤,并通过实例展示了如何分析Histogram图表和Dominator Tree。接着,针对卡顿问题,文章探讨了其产生原因,并提供了多种测试方法,包括GPU呈现模式分析、FPS Meter软件测试、绘制圆点计数法及Android Studio自带的GPU监控功能。最后,文章给出了排查卡顿问题的四个方向,帮助开发者优化应用性能。
20 4
Android性能测试——发现和定位内存泄露和卡顿
|
8天前
|
编解码 前端开发 Android开发
Android经典实战之TextureView原理和高级用法
本文介绍了 `TextureView` 的原理和特点,包括其硬件加速渲染的优势及与其他视图叠加使用的灵活性,并提供了视频播放和自定义绘制的示例代码。通过合理管理生命周期和资源,`TextureView` 可实现高效流畅的图形和视频渲染。
37 12
|
6天前
|
XML IDE 开发工具
🔧Android Studio高级技巧大公开!效率翻倍,编码不再枯燥无味!🛠️
【9月更文挑战第11天】在软件开发领域,Android Studio凭借其强大的功能成为Android开发者的首选IDE。本文将揭示一些提升开发效率的高级技巧,包括自定义代码模板、重构工具、高级调试技巧及多模块架构。通过对比传统方法,这些技巧不仅能简化编码流程,还能显著提高生产力。例如,自定义模板可一键插入常用代码块;重构工具能智能分析并安全执行代码更改;高级调试技巧如条件断点有助于快速定位问题;多模块架构则提升了大型项目的可维护性和团队协作效率。掌握这些技巧,将使你的开发之旅更加高效与愉悦。
18 5
|
12天前
|
Android开发 容器
Android经典实战之如何获取View和ViewGroup的中心点
本文介绍了在Android中如何获取`View`和`ViewGroup`的中心点坐标,包括计算相对坐标和屏幕上的绝对坐标,并提供了示例代码。特别注意在视图未完成测量时可能出现的宽高为0的问题及解决方案。
24 7
|
15天前
|
调度 Android开发 UED
Android经典实战之Android 14前台服务适配
本文介绍了在Android 14中适配前台服务的关键步骤与最佳实践,包括指定服务类型、请求权限、优化用户体验及使用WorkManager等。通过遵循这些指南,确保应用在新系统上顺畅运行并提升用户体验。
26 6
|
13天前
|
Android开发
Android经典实战之Textview文字设置不同颜色、下划线、加粗、超链接等效果
本文介绍了 `SpannableString` 在 Android 开发中的强大功能,包括如何在单个字符串中应用多种样式,如颜色、字体大小、风格等,并提供了详细代码示例,展示如何设置文本颜色、添加点击事件等,助你实现丰富文本效果。
52 3
|
13天前
|
Android开发 UED 开发者
Android经典实战之WindowManager和创建系统悬浮窗
本文详细介绍了Android系统服务`WindowManager`,包括其主要功能和工作原理,并提供了创建系统悬浮窗的完整步骤。通过示例代码,展示了如何添加权限、请求权限、实现悬浮窗口及最佳实践,帮助开发者轻松掌握悬浮窗开发技巧。
26 1
|
14天前
|
API Android开发 数据安全/隐私保护
Android经典实战之窗口和WindowManager
本文介绍了Android开发中“窗口”的基本概念及其重要性。窗口是承载用户界面的基础单位,而`WindowManager`系统服务则负责窗口的创建、更新和移除等操作。了解这些概念有助于开发复杂且用户体验良好的应用。
15 2

热门文章

最新文章