Android 内存溢出 内存泄漏(一)

转载请注明出处:http://blog.csdn.net/mr_liabill/article/details/48343803   来自《LiaBin的博客》

概念

内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory。比如在listview中不断的滚动加载图片,如果不使用LRUCache来管理图片内存缓存的话,最后程序的内存就会不断的累积达到app所能使用内存的上限,出现OOM,OOM指的是内存溢出而非内存泄漏

参考:Android 进程的内存管理分析

Android系统对dalvik的vm heapsize作了硬性限制,当java进程申请的java空间超过阈值时,就会抛出OOM异常


内存泄露 memory leak,指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存。即被分配的对象可达但已无用,
一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。举个常见的例子,单例引用activity context,因为单例模式中静态变量,是存在整个生命周期的,
所以如果此时这个静态变量持有activity context,这个activity便不能被回收即使,即使这个activity已经finish了,已经无用了,但还是不能被释放回收,可以说是内存泄漏

memory leak会最终会导致out of memory内存溢出!
从定义上可以看出内存泄露是内存溢出的一种诱因,不是唯一因素。


内存泄漏举例

最简单的例子

Object o1= new Object();
Object o2=o1;
o1=null;

o1是个强引用,然后指向堆中的一个Object对象,然后o1赋值给o2,o2同样指向这个object对象
然后,把o1置为null,本意是让这个对象不再有效,不能再被使用,但是堆中的object对象是不会被回收的,因为存在另一个强引用对象o2指向它,所以可以把它视为内存溢出

单例模式内存泄漏

有的人可能会说如果这段代码只是在一个方法域中,作用域一过,对象自然被回收释放,当然就不存在OOM,上面的例子可能并不能很好的说明这个问题
android中在编写一些类时,例如工具类,可能会编写成单例的方式,这些工具类大多需要去访问资源,也就说需要Context的参与,常常会编写为以下的格式

public class CustomManager
{
    private static CustomManager sInstance;
    private Context mContext;

    private CustomManager(Context context)
    {
        this.mContext = context;
    }

    public static synchronized CustomManager getInstance(Context context)
    {
        if (sInstance == null)
        {
            sInstance = new CustomManager(context);
        }
        return sInstance;
    }
    
    //some methods
    private void someOtherMethodNeedContext()
    {
        context.get....
    }
}

这个Context哪来的我们不能确定,很大的可能性,你在某个Activity里面为了方便,直接传了个this;这样问题就来了,我们的这个类中的sInstance是一个static且强引用的,在其内部引用了一个Activity作为Context,也就是说,我们的这个Activity只要我们的项目活着,就没有办法进行内存回收。
而我们的Activity的生命周期肯定没这么长,所以造成了内存泄漏,如果这个activity足够复杂,,,,最后极有可能造成OOM内存溢出。

至于上述的解决方案,当然是使用使用ApplicationContext,它的生命周期和我们的单例对象一致

也有人会说使用软引用/弱引用

    WeakReference<Context> mContext;
    private CustomManager(Context context)
    {
        this.mContext = new WeakReference<Context>(context);
    }
但是这样的话,activity是被回收了,如果此时还调用someOtherMethodNeedContext方法,因为context此时已经为null了,那么不就出现NullPointException了,软引用/弱引用的区别介绍参考下一篇博客

内部类-匿名内部类内存泄漏

泄漏代码1

public class MainActivity extends Activity {
    private  Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //TODO handle message...
        }
    };
    @TargetApi(11)
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler.sendMessageDelayed(Message.obtain(), 60000);
        //just finish this activity
        finish();
    }

在AS中可以看到这样的警告
This Handler class should be static or leaks might occur……

1. 当Activity finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler因为是匿名内部类默认引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露

2. 在Java中,非静态的内部类和匿名类会隐式地持有一个他们外部类的引用。静态内部类则不会

泄漏代码2

         private MyAnoHandler myAnoHandler = new MyAnoHandler(this);
         @Override
         public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            myAnoHandler.sendEmptyMessageDelayed(0, 10000);
            finish();
         } 
         class MyAnoHandler extends Handler {
		private WeakReference<MainActivity> activity;

		public MyAnoHandler(MainActivity activity) {
			this.activity = new WeakReference<MainActivity>(activity);
		}

		@Override
		public void handleMessage(Message msg) {
			System.gc();
			if (activity.get() != null) {
				Log.d("LiaBin", "MyAnoHandler handleMessage activity not null");
			} else {
				Log.d("LiaBin", "MyAnoHandler handleMessage activity is null");
			}
		}
	  }
同样会造成内存泄漏,非静态内部类默认持有外部类引用,导致MainActivity在finish之后仍然不能被GC回收。。。

10s后打印MyAnoHandler handleMessage activity not null  说明MainActivity没有被回收。。

泄漏代码3

public class AnoActivity extends AppCompatActivity {
    private TextView textView;

    //这里会报内存泄漏警告
    private final Handler myHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            Log.d("LiaBin","myhandler handlemessage");
        }
    };

    private MyThread myThread = new MyThread(myHandler);


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ano);
        textView = (TextView) findViewById(R.id.text);

        myThread.start();
        // 返回前一个Activity
        finish();
    }
}
public class MyThread extends Thread {
    private Handler handler;

    public MyThread(Handler handler) {
        this.handler = handler;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(10 * 60 * 1000);
            handler.sendMessage(handler.obtainMessage());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

这个例子跟例子1泄漏的原因又不尽相同,这里本质上是子线程持有handle引用,handle因为是匿名内部类所以默认持有context引用,子线程sleep休眠了10分钟,所以导致context不能被回收,例子1是因为Message持有handle引用,handle默认持有context引用,postdelay导致message还在,进而导致context不能被回收,但本质原因也是一样的都是因为Handler内部类持有context引用

泄漏代码4

public class MainActivity extends Activity {

	private MySecondHandler mySecondHandler;

	private TextView textview;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		textview = new TextView(this);
		setContentView(textview);
		mySecondHandler = new MySecondHandler(textview);
		mySecondHandler.sendEmptyMessageDelayed(0, 10000);
		finish();
	}

	static class MySecondHandler extends Handler {
		private TextView textview;

		public MySecondHandler(TextView textview) {
			this.textview = textview;
		}

		@Override
		public void handleMessage(Message msg) {
			System.gc();
			textview.setText("LiaBin");
		}
	}
}
以上代码,仍然会发生内存泄漏,因为虽然MySecondHandler是静态内部类,不再持有MainActivity的引用了,但是MySecondHandler却持有textview的强引用,textview持有当前上下文context的引用,textview = new TextView(this);所以即使finish也因为有强引用指向MainActivity,仍然不能被回收,内存泄漏。

最佳解决方案:

自定义一个Handler作为静态内部类,或者在ondestroy/onstop中removeCallbacksAndMessages

private MyHandler myHandler = new MyHandler(this);
        private MyHandler myHandler = new MyHandler(this);

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		myHandler.sendEmptyMessageDelayed(0, 10000);
		finish();
	}

	@Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		myHandler.removeCallbacksAndMessages(null);
	}

	// 静态内部类不持有外部类的引用
	static class MyHandler extends Handler {
		private WeakReference<MainActivity> activity;

		public MyHandler(MainActivity activity) {
			this.activity = new WeakReference<MainActivity>(activity);
		}

		@Override
		public void handleMessage(Message msg) {
			System.gc();
			if (activity.get() != null) {
				Log.d("LiaBin", "MyHandler handleMessage activity not null");
			} else {
				Log.d("LiaBin", "MyHandler handleMessage activity is null");
			}
		}
	}


一分钟之后输出:MyHandler handleMessage activity is null 

说明MainActivity被GC回收了,因为没有强引用指向它,MyHandler是个静态内部类,通知内部只是虚引用指向。。

注意这里为了演示,强势让虚拟机发生了GC,同时注意onDestroy()方法

既然MainActiviti被回收了,activity.get为null,那么再处理消息就毫无意义了

myHandler.removeCallbacksAndMessages(null);//移除所以的callback和msg


总结

发生泄漏其实都是因为该activity虽然被finish,调用了ondestroy,但是仍然有强引用对象指向它,导致该activity不能被回收,所以发生泄漏。

单例模式中发生泄漏的最严重,因为static伴随着应用的整个生命周期,只要应用进程还没被销毁,这个activity就不能被回收

handler等匿名内部类造成的泄漏,只是一段时间之内没法回收,等消息处理完后,还是可以被回收

内存溢出举例

图片解析造成内存溢出

我们在编写Android程序的时候经常要用到许多图片,不同图片总是会有不同的形状、不同的大小,但在大多数情况下,

这些图片都会大于我们程序所需要的大小。比如说系统图片库里展示的图片大都是用手机摄像头拍出来的,这些图片的分辨率会比我们手机屏幕的分辨率高得多。大家应该知道,我们编写的应用程序都是有一定内存限制的,程序占用了过高的内存就容易出现OOM(OutOfMemory)异常

在你应用程序的UI界面加载一张图片是一件很简单的事情,但是当你需要在界面上加载一大堆图片的时候,情况就变得复杂起来。在很多情况下,(比如使用ListView, GridView 或者 ViewPager 这样的组件),屏幕上显示的图片可以通过滑动屏幕等事件不断地增加,最终导致OOM。
为了保证内存的使用始终维持在一个合理的范围,通常会把被移除屏幕的图片进行回收处理。此时垃圾回收器也会认为你不再持有这些图片的引用,从而对这些图片进行GC操作。用这种思路来解决问题是非常好的,可是为了能让程序快速运行,在界面上迅速地加载图片,你又必须要考虑到某些图片被回收之后,用户又将它重新滑入屏幕这种情况。
这时重新去加载一遍刚刚加载过的图片无疑是性能的瓶颈,你需要想办法去避免这个情况的发生。

参考这篇文章   http://blog.csdn.net/guolin_blog/article/details/9316683

HandlerThread造成内存溢出

  public classMainActivity extends Activity  
    {  
        @Override  
        public void onCreate(BundlesavedInstanceState)  
        {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.activity_main);  
            Thread mThread = newHandlerThread("demo", Process.THREAD_PRIORITY_BACKGROUND);   
            mThread.start();  
			MyHandler mHandler = new MyHandler( mThread.getLooper( ) );  
			…….  
			…….  
			…….  
		}  
        @Override  
        public void onDestroy()  
        {  
			super.onDestroy();  
        }  
    } 
HandlerThread实现的run方法是一个无限循环,它不会自己结束,线程的生命周期超过了activity生命周期,
当横竖屏切换,activity被finish的时候,HandlerThread线程的数量会随着activity重建次数的增加而增加。

应该在onDestroy时将线程停止掉:mThread.getLooper().quit();


其它

1. bitmap 使用完之后,没recycle

2. listview不使用缓存的contentView

3. cursor.file使用完之后没有关闭close等

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值