您的位置:首页 > 移动开发 > Android开发

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

2015-09-10 15:17 561 查看
转载请注明出处: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等
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息