常见内存泄露原因

Context对象泄漏

1、如果一个类持有Context对象的强引用,就需要检查其生存周期是否比Context对象更长。否则就可能发生Context泄漏。

2、View持有其创建所在Context对象的引用,如果将View对象传递给其它生存周期比View所在Context更长的强引用,就可能会引起内存泄漏。

例如View#setTag(int, Object)的内存泄漏https://code.google.com/p/android/issues/detail?id=18273

3、把Context对象赋给static变量。

避免Context对象泄漏Checklist

1、检查所有持有对Context对象强引用的对象的生命周期是否超出其所持有的Context对象的生命周期。

2、检查有没有把View传出到View所在Context之外的地方,如果有的话就需要检查生命周期。

3、工具类中最好不要有Context成员变量,尽量在调用函数时直接通过调用参数传入。如果必须有Context成员变量时,可以考虑使用WeakReference来引用Context对象。

4、View持有其创建所在Context对象的引用,如果将View对象传递给其它生存周期比View所在Context更长的强引用,就可能会引起内存泄漏。

5、 检查把Context或者View对象赋给static变量的地方,看是否有Context泄漏。

6、检查所有把View放入容器类的地方(特别是static容器类),看是否有内存泄漏。7、使用WeakHashMap也需要注意有没有value-key的引用。

7、尽量使用ApplicationContext。

Handler对象泄漏

1、发送到Handler的Message实际上是加入到了主线程的消息队列等待处理,每一个Message持有其目标Handler的强引用。

如我们通常使用的匿名内部类Handler

  1. HandlermHandler = new Handler() {
  2. @Override
  3. public voidhandleMessage(Message msg) {
  4. mImageView.setImageBitmap(mBitmap);
  5. }
  6. }

上面是一段简单的Handler的使用。当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象 (通常是一个Activity)的引用,因为View会依附着一个Activity。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图 片)一起出现,这个后台线程在任务执行完毕(例如图片下载完 毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况 下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给 Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片 下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条 Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。

当然,应为是Handler对外部持有引用的原因,我们就可以将Activity设置为一个弱引用,在不必要的时候,不再执行内部方法。

  1. import android.app.Activity;
  2. importandroid.content.Context;
  3. importandroid.os.Handler;
  4. importandroid.os.Message;
  5.  
  6. importjava.lang.ref.WeakReference;
  7.  
  8. publicclass WeakRefHandler extends Handler
  9. {
  10. WeakReference<context> mWeakContext;
  11.  
  12. public WeakRefHandler(Context context)
  13. {
  14. mWeakContext = newWeakReference<context>(context);
  15. }
  16.  
  17. @Override
  18. public void handleMessage(Message msg)
  19. {
  20. if((mWeakContext.get() instanceofActivity )&& ((Activity)mWeakContext.get()).isFinishing())
  21. return ;
  22. if(mWeakContext==null){
  23. return ;
  24. }
  25. super.handleMessage(msg);
  26. }
  27. }
避免内部Getters/Setters
在Android中,虚方法调用的代价比直接字段访问高昂许多。通常根据面向对象语言的实践,在公共接口中使用Getters和Setters是有道理的,但在一个字段经常被访问的类中宜采用直接访问。
 

避免使用浮点数

通常的经验是,在Android设备中,浮点数会比整型慢两倍。

Adapter适配器
 
在Android中Adapter使用十分广泛,特别是在list中。所以adapter是数据的 “集散地” ,所以对其进行内存优化是很有必要的。
下面算是一个标准的使用模版:
主要使用convertView和ViewHolder来进行缓存处理
  1. //ViewHolder方式
  2. @Override
  3. public View getView(int position, View convertView, ViewGroup parent) {
  4. ViewHolder holder;
  5. if(convertView == null)
  6. {
  7. holder = new ViewHolder();
  8. convertView = mInflater.inflate(R.layout.list_item, null);
  9. holder.img = (ImageView)item.findViewById(R.id.img)
  10. holder.title = (TextView)item.findViewById(R.id.title);
  11. holder.info = (TextView)item.findViewById(R.id.info);
  12. convertView.setTag(holder);
  13. }else
  14. {
  15. holder = (ViewHolder)convertView.getTag();
  16. }
  17. holder.img.setImageResource(R.drawable.ic_launcher);
  18. holder.title.setText("Hello");
  19. holder.info.setText("World");
  20. }
  21.  
  22. return convertView;
  23. }
  24.  
  25. //HolderView方式
  26. @Override
  27. public View getView(int i, View convertView, ViewGroup viewGroup) {
  28. HolderView holderView;
  29.  
  30. if (convertView instanceof HolderView) {
  31. holderView = (HolderView) convertView;
  32. } else {
  33. holderView = new HolderView(mContext);
  34. }
  35. holderView.bind("标题", R.drawable.ic_launcher, "sajsa");
  36. return holderView;
  37. }
  38. public class HolderView extends GridLayout {
  39. private ImageView img;
  40. private TextView title;
  41.  
  42. public HolderView(Context context, AttributeSet attrs) {
  43. super(context, attrs);
  44. View v = LayoutInflater.from(context).inflate(R.layout.list_item, this);
  45. title = (TextView) v.findViewById(R.id.title);
  46. img = (ImageView)item.findViewById(R.id.img)
  47.  
  48. }
  49. public void bind(String stringtitle,int imgrsc, String stringinfo) {
  50. title.setText(stringtitle);
  51. img.setImageResource(imgrsc);
  52.  
  53. }
  54. }
  55.  
  56. //自己的
  57. @Override
  58. protected View getExView(int position, View convertView, ViewGroup parent) {
  59. // TODO Auto-generated method stub
  60.  
  61. NotificationLayoutItem itemView;
  62. if (convertView instanceof NotificationLayoutItem) {
  63. itemView = (NotificationLayoutItem) convertView;
  64. } else {
  65. // itemView = new NotificationLayoutItem(mContext);
  66. itemView = (NotificationLayoutItem) LayoutInflater.from(mContext)
  67. .inflate(R.layout.notificationayoutitem, null);
  68. }
  69. itemView.setData(mList.get(position));
  70. return itemView;
  71. }
池(PooL)
 

对象池:

对象池使用的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。 并非 所有对象都适合拿来池化――因为维护对象池也要造成一定开销。对生成时开销不大的对象进行池化,反而可能会出现“维护对象池的开销”大于“生成新对象的开 销”,从而使性能降低的情况。但是对于生成时开销可观的对象,池化技术就是提高性能的有效策略了。

线程池:

线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。

比如:一个应用要和网络打交道,有很多步骤需要访问网络,为了不阻塞主线程,每个步骤都创建个线程,在线程中和网络交互,用线程池就变的简单,线程池是对 线程的一种封装,让线程用起来更加简便,只需要创一个线程池,把这些步骤像任务一样放进线程池,在程序销毁时只要调用线程池的销毁函数即可。

java提供了ExecutorServiceExecutors类,我们可以应用它去建立线程池。

通常可以建立如下4种:

  1. /** 每次只执行一个任务的线程池 */
  2. ExecutorService singleTaskExecutor = Executors.newSingleThreadExecutor();
  3.  
  4. /** 每次执行限定个数个任务的线程池 */
  5. ExecutorService limitedTaskExecutor = Executors.newFixedThreadPool(3);
  6.  
  7. /** 所有任务都一次性开始的线程池 */
  8. ExecutorService allTaskExecutor = Executors.newCachedThreadPool();
  9.  
  10. /** 创建一个可在指定时间里执行任务的线程池,亦可重复执行 */
  11. ExecutorService scheduledTaskExecutor = Executors.newScheduledThreadPool(3);

引用类型:

引用分为四种级别,这四种级别由高到低依次为:强引用>软引用>弱引用>虚引用。

强引用(strong reference)
如:Object object=new Object(),object就是一个强引用了。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

软引用(SoftReference)
只有内存不够时才回收,常用于缓存;当内存达到一个阀值,GC就会去回收它;

弱引用(WeakReference)   

弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

虚引用(PhantomReference)

"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。

软引用和弱引用的应用实例:

注意:对于SoftReference(软引用)或者WeakReference(弱引用)的Bitmap缓存方案,现在已经不推荐使用了。自Android2.3版本(API Level 9)开始,垃圾回收器更着重于对软/弱引用的回收,所以下面的内容可以选择忽略。

在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术。

下面以使用软引用为例来详细说明(弱引用的使用方式与软引用是类似的):

假设我们的应用会用到大量的默认图片,而且这些图片很多地方会用到。如果每次都去读取图片,由于读取文件需要硬件操作,速度较慢,会导致性能较低。所以我 们考虑将图片缓存起来,需要的时候直接从内存中读取。但是,由于图片占用内存空间比较大,缓存很多图片需要很多的内存,就可能比较容易发生 OutOfMemory异常。这时,我们可以考虑使用软引用技术来避免这个问题发生。

首先定义一个HashMap,保存软引用对象。

  1. //首先定义一个HashMap,保存软引用对象。
  2.  
  3. private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
  4.  
  5. //再来定义一个方法,保存Bitmap的软引用到HashMap。
  6.  
  7. public void addBitmapToCache(String path) {
  8. // 强引用的Bitmap对象
  9. Bitmap bitmap = BitmapFactory.decodeFile(path);
  10. // 软引用的Bitmap对象
  11. SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);
  12. // 添加该对象到Map中使其缓存
  13. imageCache.put(path, softBitmap);
  14. }
  15.  
  16. //获取的时候,可以通过SoftReference的get()方法得到Bitmap对象。
  17.  
  18. public Bitmap getBitmapByPath(String path) {
  19. // 从缓存中取软引用的Bitmap对象
  20. SoftReference<Bitmap> softBitmap = imageCache.get(path);
  21. // 判断是否存在软引用
  22. if (softBitmap == null) {
  23. return null;
  24. }
  25. // 取出Bitmap对象,如果由于内存不足Bitmap被回收,将取得空
  26. Bitmap bitmap = softBitmap.get();
  27. return bitmap;
  28. }

使用软引用以后,在OutOfMemory异常发生之前,这些缓存的图片资源的内存空间可以被释放掉的,从而避免内存达到上限,避免Crash发生。

需要注意的是,在垃圾回收器对这个Java对象回收前,SoftReference类所提供的get方法会返回Java对象的强引用,一旦垃圾线程回收该 Java对象之后,get方法将返回null。所以在获取软引用对象的代码中,一定要判断是否为null,以免出现 NullPointerException异常导致应用崩溃。

到底什么时候使用软引用,什么时候使用弱引用呢?

个人认为,如果只是想避免OutOfMemory异常的发生,则可以使用软引用。如果对于应用的性能更在意,想尽快回收一些占用内存比较大的对象,则可以使用弱引用。

还有就是可以根据对象是否经常使用来判断。如果该对象可能会经常使用的,就尽量用软引用。如果该对象不被使用的可能性更大些,就可以用弱引用。

另外,和弱引用功能类似的是WeakHashMap。WeakHashMap对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的回收,回收以后,其条目从映射中有效地移除。WeakHashMap使用ReferenceQueue实现的这种机制。

android 内存优化一的更多相关文章

  1. 大礼包!ANDROID内存优化(大汇总)

    写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上把网上搜集的各种内存零散知识点进行汇总.挑选.简化后整理而成. 所以我将本文定义为一个工具类的文章,如果你在A ...

  2. ANDROID内存优化——大汇总(转)

    原文作者博客:转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! ANDROID内存优化(大汇总——上) 写在最前: 本文的思路主要借鉴了20 ...

  3. Android内存优化之——static使用篇(使用MAT工具进行分析)

    这篇文章主要配套与Android内存优化之——static使用篇向大家介绍MAT工具的使用,我们分析的内存泄漏程序是上一篇文章中static的使用内存泄漏的比较不容易发现泄漏的第二情况和第三种情况—— ...

  4. ANDROID内存优化(大汇总——中)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上 ...

  5. 【腾讯Bugly干货分享】Android内存优化总结&实践

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/2MsEAR9pQfMr1Sfs7cPdWQ 导语 智 ...

  6. Android内存优化(三)详解内存分析工具MAT

    前言 在这个系列的前四篇文章中,我分别介绍了DVM.ART.内存泄漏和内存检测工具的相关知识点,这一篇我们通过一个小例子,来学习如何使用内存分析工具MAT. 1.概述 在进行内存分析时,我们可以使用M ...

  7. Android内存优化1 了解java内存分配 1

    开篇废话 今天我们一起来学习JVM的内存分配,主要目的是为我们Android内存优化打下基础. 一直在想以什么样的方式来呈现这个知识点才能让我们易于理解,最终决定使用方法为:图解+源代码分析. 欢迎访 ...

  8. Android内存优化(二)DVM和ART的GC日志分析

    相关文章 Android内存优化系列 Java虚拟机系列 前言 在Java虚拟机(三)垃圾标记算法与Java对象的生命周期这篇文章中,提到了Java虚拟机的GC日志.DVM和ART的GC日志与Java ...

  9. Android内存优化(一)DVM和ART原理初探

    相关文章 Android内存优化系列 Java虚拟机系列 前言 要学习Android的内存优化,首先要了解Java虚拟机,此前我用了多篇文章来介绍Java虚拟机的知识,就是为了这个系列做铺垫.在And ...

  10. Android内存优化大全(中)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上 ...

随机推荐

  1. C++对象模型笔记之程序设计模型

    C++程序设计模型支持三种程序设计模型 1.程序模型(procedural model) 可以理解为过程化模型,就像C一样 2.抽象数据类型模型(ADT) 数据结构教材里有说过,查了下资料也不是很明确 ...

  2. laravel安装 笔记

    http://laod.cn/hosts/2015-google-hosts.html 谷歌FQIP laravel安装和设置流程 1安装composer , VirtualBox和Vagrant 下 ...

  3. C#生成word

    using Microsoft.Office.Interop.Word; using System; using System.Collections.Generic; using System.Co ...

  4. iosNSMutableAttributedString 简单操作

    // 打印系统中所有字体的类型名字    NSArray *familyNames = [UIFont familyNames];    for(NSString *familyName in fam ...

  5. Chapter 2 Open Book——10

    I sent that, and began again. 我发送了它,然后又一次重新开始写了. Mom,Everything is great. Of course it's raining. I ...

  6. Chapter 1 First Sight——26

    "Which ones are the Cullens?" I asked. "They don't look related…" 哪一个是卡伦,我问道,他们都 ...

  7. Internet History, Technology and Security (Week1)

    Week1. History: Dawn of Electronic Computing War Time Computing and Conmmunication Keywords: Electro ...

  8. Ajax交互,浏览器接收不到服务器的Json数据(跨域问题)

    该问题的情景如下: 问题描述 Ajax的请求代码放在一台机器上,而服务器的java 路由程序放在另一个机子上,所以Ajax的url填写的是带"http://"  的地址,而不是相对 ...

  9. php.ini与php-fpm.conf配置文件的区别

    php-fpm.conf是PHP-FPM特有的配置文件 php.ini是所以php模式中必须的配置文件 两者的区别是,php-fpm.conf是PHP-FPM进程管理器的配置文件,php.ini是PH ...

  10. codeforces DIV2 D 最短路

    http://codeforces.com/contest/716/problem/D 题目大意:给你一些边,有权值,权值为0的表示目前该边不存在,但是可以把0修改成另外一个权值.现在,我们重新建路, ...