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关闭线程池中的线程