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的参与,常常会编写为以下的格式
这个Context哪来的我们不能确定,很大的可能性,你在某个Activity里面为了方便,直接传了个this;这样问题就来了,我们的这个类中的sInstance是一个static且强引用的,在其内部引用了一个Activity作为Context,也就是说,我们的这个Activity只要我们的项目活着,就没有办法进行内存回收。
而我们的Activity的生命周期肯定没这么长,所以造成了内存泄漏,如果这个activity足够复杂,,,,最后极有可能造成OOM内存溢出。
至于上述的解决方案,当然是使用使用ApplicationContext,它的生命周期和我们的单例对象一致
也有人会说使用软引用/弱引用
在AS中可以看到这样的警告
This Handler class should be static or leaks might occur……
1. 当Activity finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler因为是匿名内部类默认引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。
2. 在Java中,非静态的内部类和匿名类会隐式地持有一个他们外部类的引用。静态内部类则不会
10s后打印MyAnoHandler handleMessage activity not null 说明MainActivity没有被回收。。
这个例子跟例子1泄漏的原因又不尽相同,这里本质上是子线程持有handle引用,handle因为是匿名内部类所以默认持有context引用,子线程sleep休眠了10分钟,所以导致context不能被回收,例子1是因为Message持有handle引用,handle默认持有context引用,postdelay导致message还在,进而导致context不能被回收,但本质原因也是一样的都是因为Handler内部类持有context引用
一分钟之后输出:MyHandler handleMessage activity is null
说明MainActivity被GC回收了,因为没有强引用指向它,MyHandler是个静态内部类,通知内部只是虚引用指向。。
注意这里为了演示,强势让虚拟机发生了GC,同时注意onDestroy()方法
既然MainActiviti被回收了,activity.get为null,那么再处理消息就毫无意义了
myHandler.removeCallbacksAndMessages(null);//移除所以的callback和msg
单例模式中发生泄漏的最严重,因为static伴随着应用的整个生命周期,只要应用进程还没被销毁,这个activity就不能被回收
handler等匿名内部类造成的泄漏,只是一段时间之内没法回收,等消息处理完后,还是可以被回收
图片解析造成内存溢出
我们在编写Android程序的时候经常要用到许多图片,不同图片总是会有不同的形状、不同的大小,但在大多数情况下,
这些图片都会大于我们程序所需要的大小。比如说系统图片库里展示的图片大都是用手机摄像头拍出来的,这些图片的分辨率会比我们手机屏幕的分辨率高得多。大家应该知道,我们编写的应用程序都是有一定内存限制的,程序占用了过高的内存就容易出现OOM(OutOfMemory)异常
在你应用程序的UI界面加载一张图片是一件很简单的事情,但是当你需要在界面上加载一大堆图片的时候,情况就变得复杂起来。在很多情况下,(比如使用ListView, GridView 或者 ViewPager 这样的组件),屏幕上显示的图片可以通过滑动屏幕等事件不断地增加,最终导致OOM。
为了保证内存的使用始终维持在一个合理的范围,通常会把被移除屏幕的图片进行回收处理。此时垃圾回收器也会认为你不再持有这些图片的引用,从而对这些图片进行GC操作。用这种思路来解决问题是非常好的,可是为了能让程序快速运行,在界面上迅速地加载图片,你又必须要考虑到某些图片被回收之后,用户又将它重新滑入屏幕这种情况。
这时重新去加载一遍刚刚加载过的图片无疑是性能的瓶颈,你需要想办法去避免这个情况的发生。
参考这篇文章 http://blog.csdn.net/guolin_blog/article/details/9316683
当横竖屏切换,activity被finish的时候,HandlerThread线程的数量会随着activity重建次数的增加而增加。
应该在onDestroy时将线程停止掉:mThread.getLooper().quit();
2. listview不使用缓存的contentView
3. cursor.file使用完之后没有关闭close等
概念
内存溢出 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中removeCallbacksAndMessagesprivate 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 使用完之后,没recycle2. listview不使用缓存的contentView
3. cursor.file使用完之后没有关闭close等
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories