内存泄露分析举例

内存泄露:指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

可能造成内存泄露的原因:

1、单例造成的内存泄漏:

                 public class A {
	                private static A instance;
			private Context context;

			private A(Context context) {
			    this.context = context;
			}

			public static A getInstance(Context context) {
			    if (instance != null) {
				instance = new A(context);
			    }
			    return instance;
			}
		 }

由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏。当Activity销毁后,但是单例静态不会被销毁。

正确方式

               public class AppManager {
		    private static AppManager instance;
		    private Context context;
		    private AppManager(Context context) {
			this.context = context.getApplicationContext();// 使用Application 的context
		    }
		    public static AppManager getInstance(Context context) {
			    if (instance != null) {
				instance = new AppManager(context);
			    }
			    return instance;
		    }
		}

2、Handler 造成的内存泄漏

Handler 的使用造成的内存泄漏问题应该说是最为常见了,很多时候我们为了避免 ANR 而不在主线程进行耗时操作,在处理网络任务或者封装一些请求回调等api都借助Handler来处理,但 Handler 不是万能的,对于 Handler 的使用代码编写一不规范即有可能造成内存泄漏。另外,我们知道 Handler、Message 和 MessageQueue 都是相互关联在一起的,万一 Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。
    由于 Handler 属于 TLS(Thread Local Storage) 变量, 生命周期和 Activity 是不一致的。因此这种实现方式一般很难保证跟 View 或者 Activity 的生命周期保持一致,故很容易导致无法正确释放。
     举个例子

        public class SampleActivity extends Activity {
	    private final Handler mLeakyHandler = new Handler() {
		    @Override
		    public void handleMessage(Message msg) {
		      // ...
		    }
	    }
	    @Override
	    protected void onCreate(Bundle savedInstanceState) {
		    super.onCreate(savedInstanceState);
		    // Post a message and delay its execution for 10 minutes.
		    mLeakyHandler.postDelayed(new Runnable() {
		      @Override
		      public void run() { 
			/* ... */ 
		      }
		    }, 1000 * 60 * 10);
		    // Go back to the previous Activity.
		    finish();
	    }
	}



    在该 SampleActivity 中声明了一个延迟10分钟执行的消息 Message,mLeakyHandler 将其 push 进了消息队列 MessageQueue 里。当该 Activity 被 finish() 掉时,延迟执行任务的 Message 还会继续存在于主线程中,它持有该 Activity 的 Handler 引用,所以此时 finish() 掉的 Activity 就不会被回收了从而造成内存泄漏(因 Handler 为非静态内部类,它会持有外部类的引用,在这里就是指 SampleActivity)。


    修复方法:在 Activity 中避免使用非静态内部类,比如上面我们将 Handler 声明为静态的,则其存活期跟 Activity 的生命周期就无关了。同时通过弱引用的方式引入 Activity,避免直接将 Activity 作为 context 传进去,见下面代码:

     public class SampleActivity extends Activity {
	      /**
	       * Instances of static inner classes do not hold an implicit
	       * reference to their outer class.
	       */
	      private static class MyHandler extends Handler {
		private final WeakReference<SampleActivity> mActivity;
		public MyHandler(SampleActivity activity) {
		  mActivity = new WeakReference<SampleActivity>(activity);
	      }

		@Override
		public void handleMessage(Message msg) {
		  SampleActivity activity = mActivity.get();
		  if (activity != null) {
		    // ...
		  }
		}
	      }

	      private final MyHandler mHandler = new MyHandler(this);
	      /**
	       * Instances of anonymous classes do not hold an implicit
	       * reference to their outer class when they are "static".
	       */
	      private static final Runnable sRunnable = new Runnable() {
		  @Override
		  public void run() { /* ... */ }
	      };

	      @Override
	      protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		// Post a message and delay its execution for 10 minutes.
		mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
		// Go back to the previous Activity.
		finish();
	      }
	}


综述,即推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空。

    前面提到了 WeakReference,所以这里就简单的说一下 Java 对象的几种引用类型。

    Java对引用的分类有 Strong reference, SoftReference, WeakReference, PhatomReference 四种。

    在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术。

    软/弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列可以得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软/弱引用。

    假设我们的应用会用到大量的默认图片,比如应用中有默认的头像,默认游戏图标等等,这些图片很多地方会用到。如果每次都去读取图片,由于读取文件需要硬件操作,速度较慢,会导致性能较低。所以我们考虑将图片缓存起来,需要的时候直接从内存中读取。但是,由于图片占用内存空间比较大,缓存很多图片需要很多的内存,就可能比较容易发生OutOfMemory异常。这时,我们可以考虑使用软/弱引用技术来避免这个问题发生。以下就是高速缓冲器的雏形:

    首先定义一个HashMap,保存软引用对象。

    private Map

    3、尽量避免使用 static 成员变量
        如果成员变量被声明为 static,那我们都知道其生命周期将与整个app进程生命周期一样。
        这会导致一系列问题,如果你的app进程设计上是长驻内存的,那即使app切到后台,这部分内存也不会被释放。按照现在手机app内存管理机制,占内存较大的后台进程将优先回收,yi’wei如果此app做过进程互保保活,那会造成app在后台频繁重启。当手机安装了你参与开发的app以后一夜时间手机被消耗空了电量、流量,你的app不得不被用户卸载或者静默。
        这里修复的方法是:
        不要在类初始时初始化静态成员。可以考虑lazy初始化。
    4、匿名内部类/非静态内部类和异步线程
        非静态内部类创建静态实例造成的内存泄漏
        有的时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,可能会出现这种写法:

              public class MainActivity extends AppCompatActivity {
			private static TestResource mResource = null;
			@Override
			protected void onCreate(Bundle savedInstanceState) {
				super.onCreate(savedInstanceState);
				setContentView(R.layout.activity_main);
				if(mManager == null){
					mManager = new TestResource();
				}
				//…
			}
			class TestResource {
				//…
			}
		}
这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为:

        将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请按照上面推荐的使用Application 的 Context。当然,Application 的 context 不是万能的,所以也不能随便乱用,对于有些地方则必须使用 Activity 的 Context,对于Application,Service,Activity三者的Context的应用场景如下:

        其中: NO1表示 Application 和 Service 可以启动一个 Activity,不过需要创建一个新的 task 任务队列。而对于 Dialog 而言,只有在 Activity 中才能创建


5、匿名内部类
        android开发经常会继承实现Activity/Fragment/View,此时如果你使用了匿名类,并被异步线程持有了,那要小心了,如果没有任何措施这样一定会导致泄露

                public class MainActivity extends Activity {
			…
			Runnable ref1 = new MyRunable();
			Runnable ref2 = new Runnable() {
				@Override
				public void run() {
				}
			};
			…
		}
ref1和ref2的区别是,ref2使用了匿名内部类。我们来看看运行时这两个引用的内存:

        可以看到,ref1没什么特别的。

        但ref2这个匿名类的实现对象里面多了一个引用:

        this$0这个引用指向MainActivity.this,也就是说当前的MainActivity实例会被ref2持有,如果将这个引用再传入一个异步线程,此线程和此Acitivity生命周期不一致的时候,就造成了Activity的泄露。

    
    总结

        对 Activity 等组件的引用应该控制在 Activity 的生命周期之内; 如果不能就考虑使用 getApplicationContext 或者 getApplication,以避免 Activity 被外部长生命周期的对象引用而泄露。
        尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context ),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量。
        对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
        将内部类改为静态内部类
        静态内部类中使用弱引用来引用外部类的成员变量
        Handler 的持有的引用对象最好使用弱引用,资源释放时也可以清空 Handler 里面的消息。比如在 Activity onStop 或者 onDestroy 的时候,取消掉该 Handler 对象的 Message和 Runnable.
        在 Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为 null,比如使用完Bitmap 后先调用 recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用 array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。
        正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。
        保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值