Что должно произойти с исполнителем (это локально для этого мертвого потока, никаких внешних ссылок на него)?Должен ли он быть GCed или нет?
Ответ более сложный, чем «да, это будет, если на него не будет никаких ссылок».Это зависит от того, работают ли все еще потоки, запущенные в ThreadPoolExecutor
.Это, в свою очередь, зависит от того, какой тип TPE был создан и завершены ли отправленные ему «долго выполняющиеся задачи».
Например, если задачи еще не завершены, потоки все равно будутБег.Даже если они закончили, если у вас был TPE с основными потоками, которые не установили allowCoreThreadTimeOut(true)
, то потоки не остановятся.JVM никогда не собирает мусор работающего потока, так как они считаются "корнями" GC :
... работающие потоки по определению невосприимчивы к GC.GC начинает свою работу с сканирования «корней», которые считаются всегда достижимыми;корни включают в себя глобальные переменные («статические поля» в Java-разговоре) и стеки всех запущенных потоков ...
Итак, следующий вопрос - если у потоков есть ссылки back на ThreadPoolExecutor
, и я верю, что они делают.Внутренний класс Worker
- это Runnable
, который хранится в thread.target
и выполняется Thread
, поэтому он не может быть GC'd.Worker
не static
, поэтому он подразумевает ссылки на внешний экземпляр ThreadPoolExecutor
.Метод run()
фактически вызывает метод ThreadPoolExecutor.runWorker()
, который ссылается на все очереди задач, управляемые ThreadPoolExecutor
.Таким образом, запущенные потоки хранят ссылки обратно на Worker
и TPE, поэтому сборщик мусора не может собирать TPE.
Например, вот типичный кадр стека работающего потока пула, который ссылается на TPE:
java.lang.Thread.sleep(Native Method)
com.j256.GcTester$1.run(GcTesteri.java:15)
java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
java.util.concurrent.FutureTask.run(FutureTask.java:266)
>> java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
>> java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
java.lang.Thread.run(Thread.java:748)
Если, однако, все задачи пула потоков завершены, и у него 0 основных потоков или истекло время ожидания основных потоков, тогда не было бы потоков Worker
, связанных сThreadPoolExecutor
.Тогда TPE будет собираться мусором, потому что не было никаких ссылок на него, кроме циклических, которые GC достаточно умен, чтобы обнаруживать.
Вот небольшой пример тестовой программы, которая демонстрирует это.Если имеется 1 основной поток, то TPE никогда не будет остановлен (через finalize()
), даже после того, как рабочий поток завершит работу, заметив, что файл /tmp/x
существует.Это верно, даже если основной поток не имеет ссылки на него.Однако, если имеется 0 основных потоков, то после истечения времени ожидания потока (здесь, через 1 секунду после завершения последнего задания) будет получен TPE.
public class GcTester {
public static void main(String[] args) {
int numCore = 1; // set to 0 to have it GC'd once /tmp/x file exists
ExecutorService pool =
new ThreadPoolExecutor(numCore, Integer.MAX_VALUE,
1, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()) {
protected void terminated() {
System.out.println(this + " terminated");
}
};
pool.submit(new Runnable() {
public void run() {
while (true) {
Thread.sleep(100); // need to handle exception here
if (new File("/tmp/x").exists()) {
System.out.println("thread exiting");
return;
}
}
}
});
pool = null; // allows it to be gc-able
while (true) {
Thread.sleep(1000); // need to handle exception here
System.gc(); // pull the big GC handle
}
}
}