如何快速排查解决Android中的内存泄露问题
概述
内存泄露是Android开发中比较常见的问题,一旦发生会导致大量内存空间得不到释放,可用内存急剧减少,导致运行卡顿,部分功能不可用甚至引发应用crash。对于复杂度比较高、多人协同开发的项目来讲,如何快速排查并解决内存泄露问题,往往是一个很棘手的问题,也是作为一名高级Android工程的基本技能。本文旨在简单介绍内存泄漏产生的原因,总结Android中常见的内存泄漏,重点介绍如何使用工具快速排查并解决此类问题。
Android常见内存泄露分析
Java作为一种高级语言,内存管理的任务大部分由JVM自动完成,开发者只需要遵守一定的编程规范就可以避免绝大多数问题。由于对象创建时分配内存和对象销毁时回收内存都交给JVM来处理,正常情况下当我们需要销毁一个对象时只需要消除对它的引用就可以了,JVM会在GC时把它所占用的内存自动交还给系统。但如果我们在程序中错误的持有了多余的引用,超过了其正常的使用范围,就会导致该对象以及其引用的对象无法及时释放。最极端的情况是这个对象被声明为static或者被应用的Application引用,导致其存活的时间与整个应用的生命周期相同,这样便产生了内存泄漏。
网上对于内存泄露原理的介绍比较多,这里不做过多介绍,放一篇帖讲解的比较全面,供大家参考:内存泄漏全解析,从此拒绝ANR,让OOM远离你的身边,跟内存泄漏say byebye。结合作者观点,稍做总结,括号中是正确的做法:
- 单例模式中错误引用Activity作为Context(应该使用ApplicationContext)
- 使用非静态类Handler并且未及时移除message(使用静态Handler与WeakReference,退出Activity时及时remove messages)
- 使用匿名类/非静态内部类持有外部对象引用(尽量使用private static class作为内部类)
- 集合对象未及时清理
- WebView引发的内存泄露(退出Activity时及时销毁WebView)
- ListView的Adapter中创建ItemView时未使用缓存(使用convertView和静态Holder缓存)
- 对象的注册与反注册没有成对出现造成的内存泄露(注册与反注册一定要成对出现)
工具
虽然有上面这些原则,但是开发过程中我们更多需要的是能利用工具立刻定位出问题出现的,而不是去逐行审查代码。下面介绍一些用来快速排查内存泄露问题的工具,并演示如何结合AndroidStudio开发环境使用这工具。
LeakCanary
LeakCanary是一个专门用来检测内存泄露的组件,只需要简单的配置就可以集成到项目中,在程序运行时能够自动dump内存并且生成报告。
首先我们在build.gradle中添加如下依赖:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
}
然后在Application的onCreate()方法中对LeakCanary进行初始化:
public class LeakApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
}
}
这样就完成了LeakCanary的配置。接下来我们模拟一个常见的错误,用来演示LeakCanary的用法。首先写一个错误实现的单例模式:
public class SingletonBad {
private static final String TAG = "Singleton";
private static SingletonBad instance;
private final Context mContext;
private SingletonBad(Context mContext) {
this.mContext = mContext;
}
public static SingletonBad getInstance(Context context) {
if (instance == null) {
synchronized (SingletonBad.class) {
if (instance == null) {
instance = new SingletonBad(context);
}
}
}
return instance;
}
public void doSomeThing() {
Log.d(TAG, "do something");
}
}
在MainActivity中有一个Button,点击后打开SecondActivity:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="startSecond"
android:text="start"/>
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void startSecond(View view) {
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
}
}
SecondActivity中简单调用了SingletonBad的doSomething()方法。
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
SingletonBad.getInstance(this).doSomeThing();
}
}
好了,我们启动应用进入MainActivity,点击start按钮进入到SecondActivity,再退回到MainActivity。此时LeakCanary开始工作了,弹出了一个Dump memory的提示如下图,程序会稍微卡顿。
Dump分析完成后,LeakCanary发现SecondActivity被泄露了,会在通知栏中给出提示如下图:
点击这条通知,跳转到分析报告:
怎么样,报告是不是很漂亮!我们可以清楚的看到,SingletonBad类中的静态变量instance持有了SecondActivity作为mContext,导致了内存泄露。有了LeakCanary帮我们做自动分析,内存泄露一目了然,省去了很多工作。然而LeakCanary并不是万能的,对于Activity的泄露基本都能及时发现,但是其他比较复杂的情况并不一定能分析出来,这个时候我们就需要用到MAT去做更具体的分析了。
MAT
MAT即Memory Analyzer,是一款专门用来分析内存对象的工具,可以集成到Eclipse中也能单独使用。由于现在Android开发基本都使用AndroidStudio,这里我们就使用了一个独立的版本。
首先我们先介绍一下AndroidStudio中的AndroidMonitor,一共有4项内容,分别是内存占用,cpu使用率,网络和GPU,我们今天关注的重点是第一项内存。红色框里的几个按钮我们需要用到,分别用来触发GC、Dump堆栈和跟踪内存分配。
我们继续使用上面的例子,为了让问题看起来更明显更接近真实情况,我们为SecondActivity添加一张背景图片。首先从MainActivity跳转到SecondActivity,然后按back键退回,这个时候的内存走势如下图,此时点击GC按钮清除掉没有引用的类,发现退回MainActivity后内存并没有下降。
然后点击Dump按钮,稍后便会生成一个dump文件,可以看到内存中保留的对象及其对应的count,size等,可以在此做简单的分析。
在内存中对象比较多的时候,AndroidStudio中做分析已经有些力不从心,这个时候就要我们的主角MAT登场了。
首先把前面生成的dump文件导出为一个标准的.hprof文件:在AndroidStudio的左侧边栏选择Captures选项,在Heap Snapshot下找到刚才生成的.hprof文件,右键选择“Export to standard .hprof”选项,选择保存位置即可。
然后我们打开MAT,File->Open->选择刚才保存的文件,看到如下界面:
上图中的饼图可以看出当前内存占用比例,其中Bitmap占用了28.6MB,明显是有问题的。我们点一下“Leak Suspects”按钮,生成下图:
再点击Details:
好了,现在我们能看到,是因为这个Bitmap被SecondActivity所引用,而SecondActivity又被SingletonBad引用导致,得出的结果跟我们的预期是一致的。
当然MAT中还有很多更强大的工具,比如我们可以点击“Dominator Tree”这个按钮,按照Retained Heap排序后,一个Bitmap对象排到了第一个位置。我们选中它,然后右键选中“Merge Shortest Paths to GC Roots” -> “exclude weak references”。
展开后便得到了下图:
对于前面这个操作稍作解释。我们知道JVM判断一个对象需要被GC的依据是这个对象没有路径可以通过强引用到达GC Root,通过上面这个操作我们去除了所有的weak reference,剩下的基本就是强引用了。注意最上面的SingletonBad对象左边的有一个黄色的点,表示这个对象是能够到达GC Root的,因此根据这个引用链我们可以看到,SecondActivity的背景图片最终被SingletonBad的instance引用,导致无法被回收,这便是内存泄露的根源所在。
如果对于前面的GC Root不理解的,可以去看郭霖大神这篇帖子,里面有比较详细的讲解。
本篇到这里就结束了,内存泄露是一种比较常见又不容易解决的问题,文中的例子都比较简单,实际遇到时情况可能会比这些复杂很多,但是有了这些工具的帮助,相信我们能更快的定位和处理这些问题。
如何快速排查解决Android中的内存泄露问题的更多相关文章
- 查找并修复Android中的内存泄露—OutOfMemoryError
[编者按]本文作者为来自南非约翰内斯堡的女程序员 Rebecca Franks,Rebecca 热衷于安卓开发,拥有4年安卓应用开发经验.有点完美主义者,喜爱美食. 本文系国内ITOM管理平台 One ...
- Android内存优化8 内存检测工具2 LeakCanary——直白的展现Android中的内存泄露
之前碰到的OOM问题,终于很直白的呈现在我的眼前:我尝试了MAT,但是发现不怎么会用.直到今天终于发现了这个新工具: 当我们的App中存在内存泄露时会在通知栏弹出通知: 当点击该通知时,会跳转到具体的 ...
- LeakCanary——直白的展现Android中的内存泄露
之前碰到的OOM问题,终于很直白的呈现在我的眼前:我尝试了MAT,但是发现不怎么会用.直到今天终于发现了这个新工具: 当我们的App中存在内存泄露时会在通知栏弹出通知: 当点击该通知时,会跳转到具体的 ...
- 56、LeakCanary——直白的展现Android中的内存泄露
转载:http://blog.csdn.net/watermusicyes/article/details/46333925 DEMO下载地址:https://github.com/SOFTPOWER ...
- LeakCanary Android 和 Java 内存泄露检测
说起内存泄漏还是挺让人头疼的,而且不是每个手机都会发生的情况,往往又不易察觉,那么今天我们就来介绍下LeakCanary这个工具 githup:https://github.com/square/le ...
- 系统剖析Android中的内存泄漏
[转发]作为Android开发人员,我们或多或少都听说过内存泄漏.那么何为内存泄漏,Android中的内存泄漏又是什么样子的呢,本文将简单概括的进行一些总结. 关于内存泄露的定义,我可以理解成这样 没 ...
- Android中的内存管理机制以及正确的使用方式
概述 从操作系统的角度来说,内存就是一块数据存储区域,属于可被操作系统调度的资源.现代多任务(进程)的操作系统中,内存管理尤为重要,操作系统需要为每一个进程合理的分配内存资源,所以可以从两方面来理解操 ...
- 解决Android中No resource found that matches android:TextAppearance.Material.Widget.Button.Inverse问题
解决Android中No resource found that matches android:TextAppearance.Material.Widget.Button.Inverse问题http ...
- Android DDMS检测内存泄露
Android DDMS检测内存泄露 DDMS是Android开发包中自带工具,可以测试app性能,用于发现内存问题. 1.环境搭建 参考之前发的Android测试环境搭建相关文章,这里不再复述: 2 ...
随机推荐
- java多线程断点下载原理(代码实例演示)
原文:http://www.open-open.com/lib/view/open1423214229232.html 其实多线程断点下载原理,很简单的,那么我们就来先了解下,如何实现多线程的断点下载 ...
- TCP打洞与UDP打洞的差别
为什么网上讲到的P2P打洞基本上都是基于UDP协议的打洞?难道TCP不可能打洞?还是TCP打洞难于实现? 如果如今有内网clientA和内网clientB.有公网服务端S. 如果A和B ...
- golang 查询数据库操作
SQL.Open only creates the DB object, but dies not open any connections to the database. If you want ...
- C#中Stack<T>类的使用及部分成员函数的源代码分析
Stack<T>类 Stack<T> 作为数组来实现. Stack<T> 的容量是 Stack<T> 能够包括的元素数. 当向 Stack<T&g ...
- AutoCAD如何倒角 倒圆角 倒直角
倒圆角:输f 再输r 再输入你想倒的半径,然后选相邻的两边倒直角:输chamfer 再输d 再输你想倒的距离,然后先相邻的两边 祝你成功
- Android 跑马灯效果与EditText冲突
近期一个项目,因为布局TextView内容太长了.首先想到的就是跑马灯效果,所以就把TextView又一次自己定义了,尽管跑马灯效果实现了.只是导致了还有一个问题就是EditText输入问题,当第一次 ...
- 协方差矩阵与主成分分析PCA
今天看论文,作者是用主成分分析(PCA)的方法做的.仔细学习了一下,有一篇博客写的很好,介绍的深入浅出! 协方差:http://pinkyjie.com/2010/08/31/covariance/ ...
- 在Ubuntu 12.04 LTS下成功访问Windows域共享(mount //192.168.1.102/share -o user=DOMIAN\\user,pass=passwd /mnt)
Ubuntu 12.04 LTS下成功访问Windows域共享: 1,在命令行模式下 mount //192.168.1.102/share -o user=DOMIAN\\user,pass=pas ...
- 记一次ORA-600[13011]
SunOS 5.10 Oracle 10.2.0.2.0 开发环境某一数据库出现ora-600报错. alert.log中的报错信息: Thu Nov 13 15:11:43 2014 Errors ...
- hdu 5074 Hatsune Miku DP题目
题目传送门http://acm.hdu.edu.cn/showproblem.php?pid=5074 $dp[i][j] =$ 表示数列前$i$个数以$j$结尾的最大分数 $dp[i][j] = - ...