转载请注明出处:http://blog.csdn.net/mr_liabill/article/details/48343803 来自《LiaBin的博客》
概念
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory。比如在listview中不断的滚动加载图片,如果不使用LRUCache来管理图片内存缓存的话,最后程序的内存就会不断的累积达到app所能使用内存的上限,出现OOM,OOM指的是内存溢出而非内存泄漏
Android系统对dalvik的vm heapsize作了硬性限制,当java进程申请的java空间超过阈值时,就会抛出OOM异常
一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。举个常见的例子,单例引用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等