简述内存泄漏及解决方式
来源:互联网 发布:matlab 最优化工具箱 编辑:程序博客网 时间:2024/06/16 17:11
本人还在培训期,对网络上的知识点进行归纳、总结,然后再分享给大家,有时候也会加入了一些个人的看法和见解,如果有一些谬误欢迎留言。
从我目前学习的情况看来,内存泄漏的意思大概就是:
一个代码不规范导致的,内存空间使用之后无法及时回收的问题。
案例以及分析
(案例选自百度文库)
一、非静态内部类创建静态实例
错误案例:
public class MainActivity extends Activity { static Demo sInstance = null; @Override public void onCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (sInstance == null) { sInstance = new Demo(); } } class Demo { void doSomething() { //... } }}
分析:
1、非静态内部类默认会持有外部类的引用。
2、使用非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长。
结果:内部类始终持有该Activity的引用,内部类不被释放,Activity的内存资源也不能正常回收。
二、单例模式持有外部引用
错误案例:
class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context; } public static AppManager getInstance(Context context) { if (instance != null) { instance = new AppManager(context); } return instance; }}
分析:
1、单例是静态的,生命周期和应用一样长。
2、在单例类中传入一个Context、或者Activity、或者Fragment。
结果:单例类持有了他们的引用,单例类不能被释放,那些传入的对象无法被释放
正确的单例应该修改为下面这种方式:
class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context.getApplicationContext(); } public static AppManager getInstance(Context context) { if (instance != null) { instance = new AppManager(context); } return instance; }}
分析:
传入一个context.getApplicationContext(),传入一个应用的上下文,Application生命周期跟应用相同,因此不能释放也无所谓。
注:
在单例类中使用ApplicationContext弹出Dialog似乎会报错,那时候试试回调,或者弱引用。
三、Handler造成的内存泄漏
错误案例:
public class MainActivity extends AppCompatActivity { private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { //... } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); loadData(); } private void loadData() { //...request Message message = Message.obtain(); mHandler.sendMessage(message); }}
分析:
(关于Handler的内存泄漏,细心一点的应该早就知道了,因为编译的时候,Android Studio直接就黄色警告了。)
1、首先,主线程中Looper负责消息轮询,它是一个单例类(单例的方式与众不同,这里就不增加理解难度了)。
2、Looper拥有MessageQueue(消息队列)的引用。
3、MessageQueue又拥有Handler的引用。
4、只要消息队列中的消息还没被处理,Handler就无法被释放。
解决方式(使用弱引用):
public class MainActivity extends AppCompatActivity { private MyHandler mHandler = new MyHandler(this); private TextView mTextView private static class MyHandler extends Handler { private WeakReference<Context> reference; public MyHandler(Context context) { reference = new WeakReference<>(context); } @Override public void handleMessage(Message msg) { MainActivity activity = (MainActivity) reference.get(); if (activity != null) { activity.mTextView.setText(""); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView) findViewById(R.id.textview); loadData(); } private void loadData() { //...request Message message = Message.obtain(); mHandler.sendMessage(message); }}
分析:
创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样Handler持有的对象就能被回收了。
缺陷:
这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息,
解决方式Plus(弱引用+消息队列清除):
public class MainActivity extends AppCompatActivity { private MyHandler mHandler = new MyHandler(this); private TextView mTextView private static class MyHandler extends Handler { private WeakReference<Context> reference; public MyHandler(Context context) { reference = new WeakReference<>(context); } @Override public void handleMessage(Message msg) { MainActivity activity = (MainActivity) reference.get(); if (activity != null) { activity.mTextView.setText(""); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView) findViewById(R.id.textview); loadData(); } private void loadData() { //...request Message message = Message.obtain(); mHandler.sendMessage(message); } @Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); }}
有以下方式可供选择:
一、mHandler.removeCallbacksAndMessages(null)移除消息队列中所有消息和所有的Runnable。
二、mHandler.removeCallbacks()移除回调。
三、mHandler.removeMessages()移除消息。
HandlerThread可供选择的方法:
mThread.getLooper().quit();**
四、线程造成的内存泄漏
错误案例(2个):
public void test() { //AsyncTask错误案例 new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { SystemClock.sleep(10000); return null; } }.execute(); //Thread错误案例 new Thread(new Runnable() { @Override public void run() { SystemClock.sleep(10000); } }).start(); }
分析:
原因跟Handler类似,我们要知道Thread是可以长时间执行的,一旦持有外部引用,那么那个对象就无法被回收。
而上面的异步任务和Runnable都是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。
正确的做法还是使用静态内部类的方式:
//AsyncTask正确用法static class MyAsyncTask extends AsyncTask<Void, Void, Void> { private WeakReference<Context> weakReference; public MyAsyncTask(Context context) { weakReference = new WeakReference<>(context); } @Override protected Void doInBackground(Void... params) { SystemClock.sleep(10000); return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); MainActivity activity = (MainActivity) weakReference.get(); if (activity != null) { //... } }}new MyAsyncTask(this).execute();//Thread正确用法static class MyRunnable implements Runnable { @Override public void run() { SystemClock.sleep(10000); }}new Thread(new MyRunnable()).start();
这样就避免了Activity的内存资源泄漏,当然在Activity销毁时候也应该取消相应的任务,避免任务在后台执行浪费资源。
五、注册后未反注册(观察者模式)
观察者模式:
这里强调一下观察者模式,我们要知道:它的作用的松耦合,但是关系是强引用。
在观察者模式中,所谓的注册,就是将观察者(Observe)放到被观察者(Subject)的集合当中,这就形成了观察者和被观察者之间的强引用关系。
安卓的广播是观察者模式的产品之一,所以注册广播接收器,必须解注册;在无法把握的时候,严禁将Activity、Context作为观察者。
六、Android特殊组件或者类忘记释放
比如:
1、数据库处理,Cursor忘记销毁。
2、网络编程,Socket忘记close。
3、自定义控件,TypedArray忘记recycle。
4、callback忘记remove,具体例子不好找,我记得有个网络框架有这个缺陷,但是人家改好了。
5、图片处理Bitmap忘记recycle,还有用的Bitmap就别回收了,不然程序会崩溃。
等等……
七、集合中对象没清理造成的内存泄露
某个集合类(List)被一个static变量引用,同时这个集合类没有删除自己内部的元素,这一点比较容易发现,注意不用的时候clear一下就好了。
如何阻止内存泄漏?
一、注意集合类
例如HashMap,ArrayList,等等。因为它们是内存泄漏经常发生的地方,但是发现起来也比较容易,注意clear就好了
二、弱引用、软引用的使用
就比如说,你需要APP好几处需要弹窗,而且弹窗的内容完全相同,你会希望将它封装成一个方法。
虽然我们很想避免使用Context,但是一旦封装成方法,又得考虑上Context值等问题,这个时候就可以考虑使用弱引用、或者软引用。
这里我就不展开介绍了,给出一个其他人博客的链接:Java:对象的强、软、弱和虚引用
三、观察者模式下记得解注册
解决了类间的耦合问题,但是也要记住引用问题,及时解注册,或者清空观察者集合。
三、管理自身内存的类
如果一个类管理它自己的内存,程序员应该对内存泄漏保持警惕。很多时候当一个对象的成员变量指向其他对象时,不再使用时需要被置为null。
(“置为null”这一点待考证,以前这么写过,然后被嘲笑了,但是想想也对,置为null,就不再拥有那个引用了)。
四、注意非静态内部类会持有外部类的引用
首先我们要明确:非静态内部类默认会持有外部类的引用,一旦内部类不能被释放,外部类也无法被释放,避免非静态内部类的静态实例。
五、及时停止耗时操作
这一点主要体现在Handler和Thread以及一些耗时操作上上,由于事务不能及时处理完毕,当它拥有外部引用的时候,将导致资源无法回收。
六、及时释放安卓的特殊组件
这一点体现在位图、回调对象、连接对象、游标等组件的释放上,学习过程一定反复接触过这些内容,养成良好的变成习惯,完全可以避免内存泄漏问题。
- 简述内存泄漏及解决方式
- 内存泄漏问题及解决
- 内存泄漏及管理(01)-简述GC机制和检测工具
- 内存泄漏及管理(01)-简述GC机制和检测工具
- COM常见的内存泄漏及解决
- 内存泄露&&资源泄漏及解决
- Handler内存泄漏分析及解决
- Handler内存泄漏分析及解决
- Handler内存泄漏分析及解决
- Handler内存泄漏分析及解决
- Handler内存泄漏分析及解决
- Handler内存泄漏分析及解决
- Handler内存泄漏分析及解决
- Handler内存泄漏分析及解决
- Handler内存泄漏分析及解决
- Handler内存泄漏分析及解决
- Android 内存泄漏的原因及解决
- 理解并解决IE的内存泄漏方式
- 行内元素与块级元素区别和转换
- 页面保留两位小数
- JNI笔记1
- 今天,试着写一下我的第一
- 斐波那契 多种实现 Java
- 简述内存泄漏及解决方式
- 10.05
- Linux 汇编语言开发指南
- 扩展的欧几里得算法
- 阴阳师非洲人攻略
- 380. Insert Delete GetRandom O(1)
- css实现多行文本垂直水平居中
- 剑指Offer面试题18(Java版):树的子结构
- 10.06