常见内存泄露原因
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
HandlermHandler = new Handler() { @Override public voidhandleMessage(Message msg) { mImageView.setImageBitmap(mBitmap); } }
上面是一段简单的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设置为一个弱引用,在不必要的时候,不再执行内部方法。
import android.app.Activity; importandroid.content.Context; importandroid.os.Handler; importandroid.os.Message; importjava.lang.ref.WeakReference; publicclass WeakRefHandler extends Handler { WeakReference<context> mWeakContext; public WeakRefHandler(Context context) { mWeakContext = newWeakReference<context>(context); } @Override public void handleMessage(Message msg) { if((mWeakContext.get() instanceofActivity )&& ((Activity)mWeakContext.get()).isFinishing()) return ; if(mWeakContext==null){ return ; } super.handleMessage(msg); } }
避免使用浮点数
通常的经验是,在Android设备中,浮点数会比整型慢两倍。
//ViewHolder方式 @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if(convertView == null) { holder = new ViewHolder(); convertView = mInflater.inflate(R.layout.list_item, null); holder.img = (ImageView)item.findViewById(R.id.img) holder.title = (TextView)item.findViewById(R.id.title); holder.info = (TextView)item.findViewById(R.id.info); convertView.setTag(holder); }else { holder = (ViewHolder)convertView.getTag(); } holder.img.setImageResource(R.drawable.ic_launcher); holder.title.setText("Hello"); holder.info.setText("World"); } return convertView; } //HolderView方式 @Override public View getView(int i, View convertView, ViewGroup viewGroup) { HolderView holderView; if (convertView instanceof HolderView) { holderView = (HolderView) convertView; } else { holderView = new HolderView(mContext); } holderView.bind("标题", R.drawable.ic_launcher, "sajsa"); return holderView; } public class HolderView extends GridLayout { private ImageView img; private TextView title; public HolderView(Context context, AttributeSet attrs) { super(context, attrs); View v = LayoutInflater.from(context).inflate(R.layout.list_item, this); title = (TextView) v.findViewById(R.id.title); img = (ImageView)item.findViewById(R.id.img) } public void bind(String stringtitle,int imgrsc, String stringinfo) { title.setText(stringtitle); img.setImageResource(imgrsc); } } //自己的 @Override protected View getExView(int position, View convertView, ViewGroup parent) { // TODO Auto-generated method stub NotificationLayoutItem itemView; if (convertView instanceof NotificationLayoutItem) { itemView = (NotificationLayoutItem) convertView; } else { // itemView = new NotificationLayoutItem(mContext); itemView = (NotificationLayoutItem) LayoutInflater.from(mContext) .inflate(R.layout.notificationayoutitem, null); } itemView.setData(mList.get(position)); return itemView; }
对象池:
对象池使用的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。 并非 所有对象都适合拿来池化――因为维护对象池也要造成一定开销。对生成时开销不大的对象进行池化,反而可能会出现“维护对象池的开销”大于“生成新对象的开 销”,从而使性能降低的情况。但是对于生成时开销可观的对象,池化技术就是提高性能的有效策略了。
线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。
比如:一个应用要和网络打交道,有很多步骤需要访问网络,为了不阻塞主线程,每个步骤都创建个线程,在线程中和网络交互,用线程池就变的简单,线程池是对 线程的一种封装,让线程用起来更加简便,只需要创一个线程池,把这些步骤像任务一样放进线程池,在程序销毁时只要调用线程池的销毁函数即可。
java提供了ExecutorService和Executors类,我们可以应用它去建立线程池。
通常可以建立如下4种:
/** 每次只执行一个任务的线程池 */ ExecutorService singleTaskExecutor = Executors.newSingleThreadExecutor(); /** 每次执行限定个数个任务的线程池 */ ExecutorService limitedTaskExecutor = Executors.newFixedThreadPool(3); /** 所有任务都一次性开始的线程池 */ ExecutorService allTaskExecutor = Executors.newCachedThreadPool(); /** 创建一个可在指定时间里执行任务的线程池,亦可重复执行 */ 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,保存软引用对象。
//首先定义一个HashMap,保存软引用对象。 private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>(); //再来定义一个方法,保存Bitmap的软引用到HashMap。 public void addBitmapToCache(String path) { // 强引用的Bitmap对象 Bitmap bitmap = BitmapFactory.decodeFile(path); // 软引用的Bitmap对象 SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap); // 添加该对象到Map中使其缓存 imageCache.put(path, softBitmap); } //获取的时候,可以通过SoftReference的get()方法得到Bitmap对象。 public Bitmap getBitmapByPath(String path) { // 从缓存中取软引用的Bitmap对象 SoftReference<Bitmap> softBitmap = imageCache.get(path); // 判断是否存在软引用 if (softBitmap == null) { return null; } // 取出Bitmap对象,如果由于内存不足Bitmap被回收,将取得空 Bitmap bitmap = softBitmap.get(); return bitmap; }
使用软引用以后,在OutOfMemory异常发生之前,这些缓存的图片资源的内存空间可以被释放掉的,从而避免内存达到上限,避免Crash发生。
需要注意的是,在垃圾回收器对这个Java对象回收前,SoftReference类所提供的get方法会返回Java对象的强引用,一旦垃圾线程回收该 Java对象之后,get方法将返回null。所以在获取软引用对象的代码中,一定要判断是否为null,以免出现 NullPointerException异常导致应用崩溃。
到底什么时候使用软引用,什么时候使用弱引用呢?
个人认为,如果只是想避免OutOfMemory异常的发生,则可以使用软引用。如果对于应用的性能更在意,想尽快回收一些占用内存比较大的对象,则可以使用弱引用。
还有就是可以根据对象是否经常使用来判断。如果该对象可能会经常使用的,就尽量用软引用。如果该对象不被使用的可能性更大些,就可以用弱引用。
另外,和弱引用功能类似的是WeakHashMap。WeakHashMap对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的回收,回收以后,其条目从映射中有效地移除。WeakHashMap使用ReferenceQueue实现的这种机制。