Android内存泄漏分享
内容概述
- 内存泄漏和内存管理相关基础。
- Android中的内存使用。
- 内存分析工具和实践。
以下内容不考虑非引用类型的数据,或者将其等同为对应的引用类型看待——一切皆对象。
内存泄漏概念
不再使用的对象常驻内存,如静态变量,或被其它还在使用的对象(生命周期更长)所引用的对象,对应内存无法回收利用。
为了避免对象无法正确、及时被释放,需要理解:
GC如何回收对象,如何释放对象?
对象的引用
对象的使用是通过指向它的引用被访问的,引用被保存在引用类型变量中。
这里变量
指:
类变量:静态成员变量,成员变量也叫字段。
实例变量:非静态成员变量。
局部变量:在方法中定义,赋值和使用。
不考虑:参数、返回值、常量。
在new一个对象后,其强引用被构造方法返回。
对象的内部类对象,也拥有this$0这样的强引用指向它。
Java有四种引用,分别对应不同性质的引用可达性(reachable)——可达指通过此引用访问到对应的对象。
引用的分类
强引用使用引用对应的类型变量保存,需要手动释放——设置引用变量为null。
java的有四种引用,其它三种引用由对应的引用包裹类实现——可以认为是特殊类型的引用变量,GC在对待这些引用变量时有不同的策略:
强引用(StrongReference)
正常声明的变量都是强引用,即便抛出OutOfMemoryError异常,也不会被回收。需要手动设置变量为null来释放引用。软引用(SoftReference)
仅在内存不足时GC才会回收软引用对象。弱引用(WeakReference)
一旦扫描到对象仅拥有弱引用,就回收。GC运行在一个优先级很低的线程,不会那么“及时”发现。
在通过
引用包裹对象
get获得实际对象时,有可能为null。可以使用一个ReferenceQueue来关联软引用和弱引用对象,它们在回收时其引用包裹对象被添加至此队列。
- 虚引用(PhantomReference)
不对GC产生任何影响,必须有关联的ReferenceQueue,在对象仅剩虚引用时,GC在回收它时把对应的引用包裹对象放入ReferenceQueue——通常就是用来跟踪对象的回收,做一些清理操作。不含任何“持有”对象的概念,get永远返回null。
GC何时回收对象[简要]
Java有自动的内存回收机制,在合适的时候,运行时会执行GC来清理掉那些不再被使用的对象。根据内存需要,程序运行时会不定期多次执行GC。
Java判断对象是否不再使用有多种策略,最终都是和对象的引用相关。
如果对象的引用数量为0,那么它显然是垃圾对象。
此外,Java使用“根对象可达性”来判定对象是否有效。
在虚拟机中,有一类GC相关的对象被称作“GC root”。
GC root通过引用变量一级级来找到堆中的每一个对象。很显然,不同类型的引用变量,GC对待它们有不同的发现(使用其中的引用)策略。
那些最终不能从根对象引用得到的对象被认为是不可达对象,也就是可回收对象。
可见,只有强引用需要我们自己来考虑其释放的问题。在分析内存泄漏问题时,我们主要关注对象的强引用。
对象的释放
对象的释放,就是对其强引用的释放——将保存此引用的变量设置为null。另外,若对象包含内部类对象,那么内部对象的引用也要被释放。
不同的变量它们的默认生命周期是不一样的。
- 非静态成员变量随对象的释放而释放
- 局部变量随方法结束释放
- 静态成员变量随进程结束而释放。
都可以“手动”设置为null来释放。
方法未返回前,执行域的变量都不会释放。需要注意一些方法中的变量的及时释放。
void releaseObject() {
Person p = new Person();
p = new Person(); // 释放
p = null; // 释放
// more code...
}
void uglyMethod() {
Task task;
while(!stop) {
task = mBlockingQueue.take(); // 阻塞
//一些针对task的操作。
}
}
上面,在take()获得下一个对象赋给task之前,task一直引用着上一个从队列中获得的Task对象——它无法被释放。
引用的方向
引用指向某个对象。
A持有B的引用,那么此引用的方向从A到B。
A不可释放,A引用B,那么B也不可释放。反之,B引用到了不可释放的A,对B的释放没有影响。
Outgoing Reference:
对于一个对象,查看它拥有的引用变量,可以知道它所引用的其它对象。
Incoming Reference:
其它对象持有的指向当前对象的引用变量。
环引用
若A和B互相引用,这两个对象则形成一个环形引用,但不是根对象可达,环形引用是可以被正常回收的。
Android中的内存使用
- Android程序有内存限制。
- 频繁的GC容易造成程序响应问题。
- 进程自动回收:运行在后台的程序,拥有的内存越大,越容易被回收——任务栈和进程的关系——做好数据持久化、程序状态连续性和恢复。
对象使用的建议
Android程序偏向更轻量级的对象,更少的内存占用时间(除去必要的内存缓存),重用避免重复创建。
避免使用枚举
使用final static int。多使用final修饰
除非业务需要,首选final修饰,编译器会优化。图片
成熟的库(Android-Universal-Image-Loader),用多少取多少,及时释放,缓存。软引用和弱引用
能满足需要的话,代替强引用。池和对象复用
避免对象创建,引起内存抖动。例如知道一个集合是固定大小的话,那么每次网络请求结束后更新对象字段值,而不是clear又创建一批新对象。
线程池——好处不多说。使用时注意因为run持久不结束,线程对象对应的字段和局部变量注意泄漏。
Adapter中数据对象
和View的复用。UI操作的去噪
快速滑动、输入等。避免不必要的getter、setter
仅仅是简单的POJO,完全没必要访问控制器。合并handler
handler不要离开Activity,最好的一个Activity使用一个就够了。不要使用Handler代替回调来通信,使用第三方库,如EventBus来解耦,handler传递数据很低效(不及时-它不是同步的,对象序列化)。
handler是用来完成跨线程的通信的。及时释放引用
能使用局部变量的,就不要使用字段。方法中,释放那些不使用又继续占有的对象引用。
四大组件对象不是由我们new的,有其明确的生命周期,在“销毁”动作时从对象引用层面释放该释放的。内部类
优先使用静态内部类。
匿名内部类总是默认持有外部类对象的引用。在保证速度的前提下使用文件缓存
一些情况下甚至是必须的,如登录状态。使用ApplicationContext
仅在必要的时候——如dialog——使用Activity,而且注意Activity的Context的及时释放。使用具体类而不是接口
例如,HashMap,变量不需要声明为Map,这会有更好的执行速度。
没必要为“不存在”的扩展性做牺牲。在onDestroy中做好清理
主要是引用的释放,广播的取消注册,回调/监听对象的解除,handler的取消投递的消息、网络请求的取消、动画的停止,线程、其它异步任务和处理等。
“最佳实践”平时多收集,原则上
:
对于泄漏问题,只有一点,不使用就及时把保持引用的成员变量和局部变量设置为null。重点注意回调和静态字段。
常见的泄漏
典型大对象
- Activity
- 图片、音频、视频文件
- Json数据
可以从Activity开始,依次排查占用内存较大的对象的泄漏。通常,一个包含更多其它对象的大对象的释放,顺带解决了很多对象的泄漏。
匿名内部类
网络,语音,线程,其它异步操作,如果使用到callBack/Listener对象,应该注意这些对象的释放。
场景:
AudioManager是全局的语音管理对象。
假设播放需要传递语音文件路径并提供回调来控制UI:
在Activity中:
void onCreate() {
AudioManager.addListener(new AudioPlayCallBack() {
@Override
public void startPlay() {
}
@Override
public void stopPlay() {
}
});
}
void onPlayButtonClick() {
AudioManager.startPlay(mAudioPath);
}
void onDestroy() {
AudioManager.clearListener();
AudioManager.stopPlay();
}
监听(观察者模式),广播接收器
同回调一样,一般的,Activity中使用Receiver或Observer对象,在onCreate中开始注册,在onDestroy中需要解除注册。
Service
作为四大组件之一,对象本身创建和释放不是我们控制。使用startService和stopService、bingService和unBindService来控制组件对象的生命周期。
通常服务是一直运行在后台的,避免在服务中保存不使用的对象。
场景:
ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mCoreService = ((CoreService.CoreServiceBinder) service).getService();
mCoreService.registerConnectionStatusCallback(new IConnectionStatusCallback() {
@Override
public void connectionStatusChanged(int connectedState, String reason) {
// xxx
}
});
// xxx
}
/**
在和Service的连接“意外”中断时执行,通常是运行Service的其它进程崩溃后引起。
同一进程中几乎不会发生(Service死掉了,而此处代码还在执行...):此方法几乎不会被执行。
不会移除此连接。必须主动调用unbindService来解除连接。
*/
@Override
public void onServiceDisconnected(ComponentName name) {
mCoreService.unRegisterConnectionStatusCallback(); // 不执行
mCoreService = null;// 不执行
}
};
上面应该在onDestroy中unbindService并移除Activity和Service对象的引用(回调匿名内部类)连接。
Handler
延迟消息被线程中的MessageQueue持有,在消息未处理前,Message对象引用handler,而handler引用Activity的事情很容易发生。
handler大多数时间也是写为匿名内部类——这本身没什么。
在onDestroy中:
void onDestroy() {
handler.removeCallbacksAndMessages(null);
}
Context
Android中Context是“God Object”,它拥有很多运行时需要的全局信息。通常使用第三方库,系统API时,需要一个
Context时,优先使用Application。如果必须用到Activity的情况,记得它和匿名内部类是一样的,不要在三番五次的参数传递之后,忘记释放。
动画
属性动画必须手动stop,否则它会一直执行下去,持有Activity的mContext导致Activity对象的泄漏。
单例、全局对象
少用,注意意外的引用驻留。
简单的:
ActivityManager管理Activity的集合,在onCreate和onDestroy时从ActivityManager中add和remove掉。
类变量如果是内部类这样的拥有对外部类的引用:
记得释放类变量,或者换用静态内部类,普通类,然后提供对外部类引用的设置和解除。
总而言之:对象是有生命周期的,需要在合适的时间释放对象的强引用。
内存分析工具
学习内存分析工具的使用,在实践中积累内存泄漏的问题,避免错误的代码。
Android Monitor
Android Studio 1.5以上版本有此功能。
可以快速查看对象个数,占用内存情况,“简单地”分析对象引用情况。
Memory Analysis Tool
Java的内存分析工具。
Shallow vs. Retained Heap
Dominator Tree
LeakCanary
产生和发现引用泄漏
检测目标类
运行程序,GC后dump生成hprof文件,使用MAT分析。
全面测试
在测试环境,使用LeakCanary实时监测。
讨论
列表+详情页 详情页 使用SingleInstance?不允许多个页面同时打开?
Volley Dispatcher引起的泄露。
收集踩过的坑。
Android内存泄漏分享的更多相关文章
- Android内存泄漏检测利器:LeakCanary
Android内存泄漏检测利器:LeakCanary MAR 28TH, 2016 是什么? 一言以蔽之:LeakCanary是一个傻瓜化并且可视化的内存泄露分析工具 为什么需要LeakCanary? ...
- Android 内存泄漏总结(转)
Android 内存泄漏总结 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题.内存泄漏大家都不陌生了,简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有却 ...
- 【转】android 内存泄漏相关收藏博客。
关于android内存泄漏的研究 博客建了几个月,都没有去写,一是因为当时换工作,然后又是新入职(你懂的,好好表现),比较忙:二是也因为自己没有写博客的习惯了.现在还算是比较稳定了,加上这个迭代基 ...
- 关于android内存泄漏的研究
博客建了几个月,都没有去写,一是因为当时换工作,然后又是新入职(你懂的,好好表现),比较忙:二是也因为自己没有写博客的习惯了.现在还算是比较稳定了,加上这个迭代基本也快结束了,有点时间来写写博客.好了 ...
- Android内存泄漏的各种原因详解
转自:http://mobile.51cto.com/abased-406286.htm 1.资源对象没关闭造成的内存泄漏 描述: 资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我 ...
- Android 内存泄漏优化汇总
android内存泄漏优化摘要 博客分类: android android内存溢出OutOfMemoryError . android移动应用程序的内存分配一般是8凯瑟琳约,不正确地假定处理内存处理非 ...
- Android 内存泄漏分析与解决方法
在分析Android内存泄漏之前,先了解一下JAVA的一些知识 1. JAVA中的对象的创建 使用new指令生成对象时,堆内存将会为此开辟一份空间存放该对象 垃圾回收器回收非存活的对象,并释放对应的内 ...
- [Android]Android内存泄漏你所要知道的一切(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/7235616.html Android内存泄漏你所要知道的一切 ...
- LeakCanary 来检查 Android 内存泄漏
LeakCanary 来检查 Android 内存泄漏
随机推荐
- PHP反射API
近期忙着写项目,没有学习什么特别新的东西,所以好长时间没有更新博客.我们的项目用的是lumen,是基于laravel的一个轻量级框架,我看到里面用到了一些反射API机制来帮助动态加载需要的类.判断方法 ...
- 《Python数据分析》环境搭建之安装Jupyter工具(一)
(免责声明:本文档是针对Python有经验的用户,如果您对Python了解很少,或者从未使用,建议官方教程用Anaconda安装) 前期准备:Python环境 虽然Jupyter可以运行多种编程语言, ...
- ECMAScript 5
2009年12月,ECMAScript 5.02011年6月,ECMAscript 5.1版发布2015年6月,ECMAScript 6正式通过,成为国际标准ES6第一个版本 ES2015,发布于20 ...
- OpenCv遍历图像小结
参考:http://www.cnblogs.com/ronny/p/opencv_road_2.html http://blog.csdn.net/xiaowei_cqu/article/detail ...
- Vmware无法获取快照信息 锁定文件失败
今天早上起来发现虚拟机崩了: 造成原因: 如果使用VMWare虚拟机的时候突然系统崩溃蓝屏,有一定几率会导致无法启动, 会提示:锁定文件失败,打不开磁盘或快照所依赖的磁盘: 这是因为虚拟机在运行的时候 ...
- openfl使用64位的ndk时,编译报错的问题!
当使用64位的ndk时,如果使用openfl test android运行android测试,应该会出现 arm-linux-androideabi-g++:找不到这个命令的错误. 原因是,haxel ...
- 第二天ci项目规划 前后台分离
第二天ci项目规划 前后台分离 1/31/2016 2:40:26 PM 前后台 表面上看前后台不同网站 但是数据是他们之间的联系要完成结构完整项目 设计好前后台 基于mvc框架 前后台那些地方不同 ...
- Android 照相 滤镜
android-image-filter 19种相片滤镜,使用也简单,all filters in file BitmapFilter.java : Bitmap newBitmap = Bitmap ...
- 【历史】JavaScript和Java没啥关系!————JavaScript简史
文章的开始先上张图: 图片拍摄自北京图书大厦,代表着现在国内应该是绝大部分书店的现状--Javascript书籍放在Java类当中.甚至很多业内人也一直认为Javascript是Java语言在浏览器内 ...
- spark shuffle 相关细节整理
1.Shuffle Write 和Shuffle Read具体发生在哪里 2.哪里用到了Partitioner 3.何为mapSideCombine 4.何时进行排序 之前已经看过spark shuf ...