Androdid中ExecutorService内存泄露原因分析

来源:互联网 发布:苹果mac爱奇艺视频 编辑:程序博客网 时间:2024/06/05 14:41

Androdid中ExecutorService内存泄露原因分析

  我们都知道ExecutorService开启线程用于实现异步逻辑,ExecutorService维护一个线程池,但是因为我们大多数时候使用ExecutorService创建的线程的生命周期跟应用app的进程是一致的,所以没有造成任何的内存泄露的问题,但是实际上ExecutorService的使用如果不小心会给我们造成麻烦。所以本文对ExecutorService使用造成的内存泄露做了源码的分析(即分析线程池内部的线程为什么处于活着的状态在runnable执行完成之后)。
假定使用ExecutorService的代码如下:

    public static void testExecutorService() {            ExecutorService executorService = Executors.newFixedThreadPool(1);            executorService.execute(new Runnable() {        @Override        public void run() {            //do something        }    });}

  函数内部实例化局部变量,通常按照我们的理解是在函数执行完成,出了executorService变量的作用域,就会被回收内存(假定runnable已经执行结束),但是实际上结果不是这样子的,因为executorService变量创建的线程池中的线程还处于活着的状态,通常我们创建一个Thread并执行runnable的时候,在runnable执行结束的时候,thread会处于die的状态,并内存得到回收,接下去分析下为什么线程会处于活着的状态。首先我们查看ExecutorService的实现类ThreadPoolExecutor,可以看到ThreadPoolExecutor维护Worker的一个集合
  
这里写图片描述

  Worker对象如:

这里写图片描述

  并且查看ThreadPoolExecutor的execute方法
  这里写图片描述
  
  可以确定executorService对象持有内部线程池维护的Worker Set集合,而Worker内部持有一个thread和一个runnable,Worker本身也是一个runnable,如果曾经我们执行过execute的话,那他们之间的对应关系如下(当executorService没有出作用域的时候):
  这里写图片描述
  当未出函数执行作用域的时候executorService和thread均被gc root索引,当出函数作用域的时候,我们能够确定executorService索引已经释放,并且如开头所说thread还处于活着的状态(使用Android stuido查看是处于wait状态),分析如下:
  查看 execute函数,当当前runnable执行的数量小于我们设置的核心线程数量的时候,如下if判断语句为true

    if (workerCountOf(c) < corePoolSize) {        if (addWorker(command, true))            return;        c = ctl.get();    }

函数执行addWorker(只抓取核心部分代码)

    private boolean addWorker(Runnable firstTask, boolean core) {    try {        w = new Worker(firstTask);        final Thread t = w.thread;        if (t != null) {            final ReentrantLock mainLock = this.mainLock;            mainLock.lock();            try {                    workers.add(w);            } finally {                mainLock.unlock();            }            if (workerAdded) {                ***t.start();***                workerStarted = true;            }        }    } finally {        if (! workerStarted)            addWorkerFailed(w);    }    return workerStarted;}

接下去查看t.start(),从前面我们知道,此处的thread持有的runnable对象是worker,所以t.start执行的是worker的run方法,最后执行runworker

final void runWorker(Worker w) {        try {            while (task != null || (task = getTask()) != null) {                try {                    try {                        task.run();                    } catch (RuntimeException x) {                        thrown = x; throw x;                    } catch (Error x) {                        thrown = x; throw x;                    } catch (Throwable x) {                        thrown = x; throw new Error(x);                    } finally {                        afterExecute(task, thrown);                    }                } finally {                    task = null;                }            }            completedAbruptly = false;        } finally {            processWorkerExit(w, completedAbruptly);        }    }

   其中task是外部传入的runnable对象,我们看到此处有一个while循环,当当前的task执行完毕后,task为null,所以循环的执行我们到函数getTask查看

  private Runnable getTask() {        boolean timedOut = false;        for (;;) {            int c = ctl.get();            int rs = runStateOf(c);            // Check if queue empty only if necessary.            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {                decrementWorkerCount();                return null;            }            int wc = workerCountOf(c);            // Are workers subject to culling?            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;            if ((wc > maximumPoolSize || (timed && timedOut))                && (wc > 1 || workQueue.isEmpty())) {                if (compareAndDecrementWorkerCount(c))                    return null;                continue;            }            try {                Runnable r = timed ?                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :                    workQueue.take();                if (r != null)                    return r;                timedOut = true;            } catch (InterruptedException retry) {                timedOut = false;            }        }    }

   其中我们看到workQueue.take();,我们知道队列的take方法在队列为空的情况下会阻塞线程。
   至此,分析结论是线程池内部的核心线程被队列的take阻塞。文章只做简单分析,相关部分需要还得查看源码。针对该问题修改:针对移动端建议不使用Executors.newFixedThreadPool()创建ExecutorService,改用Executors.newCachedThreadPool(),同时申请创建的线程池,如果生命周期不跟进程的生命周期一样,建议手动调用shutdown关闭线程池中的线程

原创粉丝点击