Android中使用Rxjava时,内存泄漏了吗?

kpns9444 8年前
   <p>今天有位同学问了我一个问题,话说,问我</p>    <blockquote>     <p>“有遇到网络请求一半,退出Activity造成的Theard泄露吗?已在销毁时调用了un了</p>    </blockquote>    <p>我去查看了下rx的源码的unsubscribe方法,定位到一个实现类,NewThreadWorker的unsubscribe方法中,源码如下:</p>    <pre>  <code class="language-java">@Override      public void unsubscribe() {          isUnsubscribed = true;          executor.shutdownNow();          deregisterExecutor(executor);      }        @Override      public boolean isUnsubscribed() {          return isUnsubscribed;      }</code></pre>    <p>这个shutdownNow()在java的注释中写的很清楚</p>    <blockquote>     <p>There are no guarantees beyond best-effort attempts to stop* processing actively executing tasks.</p>    </blockquote>    <pre>  <code class="language-java">public interface ExecutorService extends Executor {        /**       * Initiates an orderly shutdown in which previously submitted       * tasks are executed, but no new tasks will be accepted.       * Invocation has no additional effect if already shut down.       *       * <p>This method does not wait for previously submitted tasks to       * complete execution.  Use {@link #awaitTermination awaitTermination}       * to do that.       */      void shutdown();        /**       * Attempts to stop all actively executing tasks, halts the       * processing of waiting tasks, and returns a list of the tasks       * that were awaiting execution.       *       * <p>This method does not wait for actively executing tasks to       * terminate.  Use {@link #awaitTermination awaitTermination} to       * do that.       *       * <p>There are no guarantees beyond best-effort attempts to stop       * processing actively executing tasks.  For example, typical       * implementations will cancel via {@link Thread#interrupt}, so any       * task that fails to respond to interrupts may never terminate.       *       * @return list of tasks that never commenced execution       */      List<Runnable> shutdownNow();</code></pre>    <p>它只是尽量保证停止所有tasks,因为,如果耗时操作没有做完,finished掉activity,同时unsubscribe 掉Subscription的话,可能还有后台线程在做一些耗时任务。那么会不会造成内存泄露呢?</p>    <p>我觉得还是要源码来说说话吧</p>    <pre>  <code class="language-java"> /**       * Unsubscribe from all of the subscriptions in the list, which stops the receipt of notifications on       * the associated {@code Subscriber}.       */      @Override      public void unsubscribe() {          if (!unsubscribed) {              List<Subscription> list;              synchronized (this) {                  if (unsubscribed) {                      return;                  }                  unsubscribed = true;                  list = subscriptions;                  subscriptions = null;              }              // we will only get here once              unsubscribeFromAll(list);          }      }        private static void unsubscribeFromAll(Collection<Subscription> subscriptions) {          if (subscriptions == null) {              return;          }          List<Throwable> es = null;          for (Subscription s : subscriptions) {              try {                  s.unsubscribe();              } catch (Throwable e) {                  if (es == null) {                      es = new ArrayList<Throwable>();                  }                  es.add(e);              }          }          Exceptions.throwIfAny(es);      }</code></pre>    <p>实际上会做一些清除引用,暂停任务的操作。因此,一般来讲在activity的ondestroy中调用unsubscribe之后,是不会造成内存泄露的。但是真的是这样的吗?让我们来做做实验吧,结果说明一切。</p>    <p>为了能够更好的证明这一点,我还特意做了一个app demo去验证。</p>    <p>主要代码:</p>    <p><img alt="你使用Rxjava时,内存泄漏了吗?" src="https://simg.open-open.com/show/1cf779f445fd38415cbaf1f3ffd1b346.png"></p>    <p><img alt="你使用Rxjava时,内存泄漏了吗?" src="https://simg.open-open.com/show/5fa42414cea0e1c48ed951b004217499.png"></p>    <p>demo地址已经放到了github上: <a href="/misc/goto?guid=4959675391706848456">内存泄露分析</a></p>    <p>启动app,首先进入的是MainActivity,然后,我们进入SecondActivity这时候,onresume执行,我们的任务也就开始了,稍微过几秒,我们退出SecondActivity,回到MainActivity,这之后,显然,SecondActivity的ondestory方法会被执行,我们可以发现日志也停止了打印。</p>    <p><img alt="你使用Rxjava时,内存泄漏了吗?" src="https://simg.open-open.com/show/0280bf4ec3c9c1e5f0847a8ec1e6eb48.png"></p>    <p>耗时任务正在执行</p>    <p>如上图所示,停留在了5就不在执行了。</p>    <p>这时候,我们GC一下,在导出<code>hprof</code>文件,注意,为什么要手动GC一下呢?因为android虚拟机去GC也是有策略的,有GC周期的。这时候可能并没有GC过,也就是说,SecondActivity的内存可能并没有被释放,但并不等于说,SecondActivity就泄露了,因为他也有可能是可以被GC的,只是还没有来得及被GC而已。总之,在导出<code>hprof</code>文件之前,最好先手动GC一下。就是下图那个车子,嗯,点一下吧,放心点。</p>    <p><img alt="你使用Rxjava时,内存泄漏了吗?" src="https://simg.open-open.com/show/c741779017d8180dc5a0ed1b59098d5d.png"></p>    <p>然后熟悉查看<code>hprof</code>文件的同学这时候可能就看到了,执行分析之后,并没有看到内存泄露。</p>    <p><img alt="你使用Rxjava时,内存泄漏了吗?" src="https://simg.open-open.com/show/7986bb7b97f692239bcddd1c4cb95666.png"></p>    <p>从上图我们看到SecondeActivity已经是红色,标明被回收了,不存在内存泄漏。</p>    <p>同时,我调试跟踪了一下<code>unsubscribe</code>调用栈,因为是一堆抽象类及接口,又有一堆的实现类,所以,最效率的方法还是调试跟踪,这时候路径就出来了,具体怎么个调发请看我的手稿,比较粗糙。</p>    <p>最终发现,最后的根源就是<code>hander</code> 的 <code>removeCallbacksAndMessages</code> 方法被调用了。</p>    <p><img alt="你使用Rxjava时,内存泄漏了吗?" src="https://simg.open-open.com/show/d349650027639f9c02afa9d4330a5efe.png"></p>    <p><img alt="你使用Rxjava时,内存泄漏了吗?" src="https://simg.open-open.com/show/e35140f3e80eecd4f56db146f820cac9.png"></p>    <blockquote>     <p>因此是不存在内存泄漏的,是这样的吗??让我们来在看一个例子!</p>    </blockquote>    <p>假如耗时任务本来就是一个异步任务呢?</p>    <p><img alt="你使用Rxjava时,内存泄漏了吗?" src="https://simg.open-open.com/show/ab00ac265a23b9bea72cc8fc50beb168.png"></p>    <p>修改一下</p>    <p>跑几秒钟,然后回到MainActivity,这时候你在GC一下,hprof文件导出看看,我去,泄漏了。</p>    <p><img alt="你使用Rxjava时,内存泄漏了吗?" src="https://simg.open-open.com/show/473e9c3754dfd08222d936e9950eb0e1.png"></p>    <p>真的泄漏了</p>    <p>在看看控制台,我去,一直执行到跑完。</p>    <p><img alt="你使用Rxjava时,内存泄漏了吗?" src="https://simg.open-open.com/show/e4d6132bdb0321638fc0c73f4e1b697c.png"></p>    <p>一直跑完了</p>    <p>然后等跑完了,在GC一下,在导出hprof文件看看,内存泄漏依然还在,SecondActivity永久放入了你的内存,知道APP进程死掉才会释放。</p>    <p>不信的话,可以多启动SecondActivity,退出SecondActivity几次 ,你会发现内存不断飙升~~</p>    <p><img alt="你使用Rxjava时,内存泄漏了吗?" src="https://simg.open-open.com/show/0133d4e68e6b57911c8eaf6ab025392d.png"></p>    <p>内存不断飙升</p>    <p>我继续在思考,是不是这个耗时任务本身就丢在一个线程中执行,所以,如果我们rx不切换线程,是不是就不会泄露呢?</p>    <p>所以,还是不服,在改改代码,继续~</p>    <p><img alt="你使用Rxjava时,内存泄漏了吗?" src="https://simg.open-open.com/show/d338d0929dbf4c04ea3481b9de310f72.png"></p>    <p>rx不切换线程了,反正是在非主线程做耗时操作</p>    <p>结果,并没有什么卵用,和上述情况一致,多次启动、关闭SecondActivity ,你会发现内存一样会飙升,GC后,导出<code>hprof</code>文件看看,一样泄露了了。</p>    <p><strong>所以,大家应该懂了使用 rx的正确知识,自己的任务都同步写,线程切换交给Rx,因为Rx更懂你~~。</strong></p>    <p><br> <a href="/misc/goto?guid=4959675391805206389">阅读原文</a></p>